mz_repr/
catalog_item_id.rs1use std::fmt;
11use std::str::FromStr;
12
13use anyhow::{Error, anyhow};
14use mz_proto::{RustType, TryFromProtoError};
15#[cfg(any(test, feature = "proptest"))]
16use proptest_derive::Arbitrary;
17use serde::{Deserialize, Serialize};
18
19include!(concat!(env!("OUT_DIR"), "/mz_repr.catalog_item_id.rs"));
20
21#[derive(
23 Clone,
24 Copy,
25 Debug,
26 Eq,
27 PartialEq,
28 Ord,
29 PartialOrd,
30 Hash,
31 Serialize,
32 Deserialize
33)]
34#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
35pub enum CatalogItemId {
36 System(u64),
38 IntrospectionSourceIndex(u64),
40 User(u64),
42 Transient(u64),
44}
45
46impl CatalogItemId {
47 pub fn is_system(&self) -> bool {
49 matches!(
50 self,
51 CatalogItemId::System(_) | CatalogItemId::IntrospectionSourceIndex(_)
52 )
53 }
54
55 pub fn is_user(&self) -> bool {
57 matches!(self, CatalogItemId::User(_))
58 }
59
60 pub fn is_transient(&self) -> bool {
62 matches!(self, CatalogItemId::Transient(_))
63 }
64}
65
66impl FromStr for CatalogItemId {
67 type Err = Error;
68
69 fn from_str(mut s: &str) -> Result<Self, Self::Err> {
70 if s.len() < 2 {
71 return Err(anyhow!("couldn't parse id {}", s));
72 }
73 let tag = s.chars().next().unwrap();
74 s = &s[tag.len_utf8()..];
75 let variant = match tag {
76 's' => {
77 if Some('i') == s.chars().next() {
78 s = &s[1..];
79 CatalogItemId::IntrospectionSourceIndex
80 } else {
81 CatalogItemId::System
82 }
83 }
84 'u' => CatalogItemId::User,
85 't' => CatalogItemId::Transient,
86 _ => return Err(anyhow!("couldn't parse id {}", s)),
87 };
88 let val: u64 = s.parse()?;
89 Ok(variant(val))
90 }
91}
92
93impl fmt::Display for CatalogItemId {
94 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95 match self {
96 CatalogItemId::System(id) => write!(f, "s{}", id),
97 CatalogItemId::IntrospectionSourceIndex(id) => write!(f, "si{}", id),
98 CatalogItemId::User(id) => write!(f, "u{}", id),
99 CatalogItemId::Transient(id) => write!(f, "t{}", id),
100 }
101 }
102}
103
104impl RustType<ProtoCatalogItemId> for CatalogItemId {
105 fn into_proto(&self) -> ProtoCatalogItemId {
106 use proto_catalog_item_id::Kind::*;
107 ProtoCatalogItemId {
108 kind: Some(match self {
109 CatalogItemId::System(x) => System(*x),
110 CatalogItemId::IntrospectionSourceIndex(x) => IntrospectionSourceIndex(*x),
111 CatalogItemId::User(x) => User(*x),
112 CatalogItemId::Transient(x) => Transient(*x),
113 }),
114 }
115 }
116
117 fn from_proto(proto: ProtoCatalogItemId) -> Result<Self, TryFromProtoError> {
118 use proto_catalog_item_id::Kind::*;
119 match proto.kind {
120 Some(System(x)) => Ok(CatalogItemId::System(x)),
121 Some(IntrospectionSourceIndex(x)) => Ok(CatalogItemId::IntrospectionSourceIndex(x)),
122 Some(User(x)) => Ok(CatalogItemId::User(x)),
123 Some(Transient(x)) => Ok(CatalogItemId::Transient(x)),
124 None => Err(TryFromProtoError::missing_field("ProtoCatalogItemId::kind")),
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use proptest::prelude::*;
132
133 use super::*;
134
135 #[mz_ore::test]
136 fn proptest_catalog_item_id_roundtrips() {
137 fn testcase(og: CatalogItemId) {
138 let s = og.to_string();
139 let rnd: CatalogItemId = s.parse().unwrap();
140 assert_eq!(og, rnd);
141 }
142
143 proptest!(|(id in any::<CatalogItemId>())| {
144 testcase(id);
145 })
146 }
147
148 #[mz_ore::test]
149 fn test_catalog_item_id_from_str_non_ascii() {
150 for invalid in ["ü1", "ü", "é42", "🦀7", ""] {
153 assert!(
154 invalid.parse::<CatalogItemId>().is_err(),
155 "expected {invalid:?} to fail to parse"
156 );
157 }
158 }
159}