edge_schema/schema/entity/
uri.rs1#[derive(Clone)]
13pub struct EntityUri {
14 value: String,
15
16 type_start: usize,
17 version_start: usize,
18 name_start: usize,
19}
20
21impl PartialEq for EntityUri {
22 fn eq(&self, other: &Self) -> bool {
23 self.value == other.value
24 }
25}
26
27impl std::fmt::Debug for EntityUri {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.debug_tuple("EntityUri").field(&self.value).finish()
30 }
31}
32
33impl Eq for EntityUri {}
34
35impl PartialOrd for EntityUri {
36 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
37 Some(self.value.cmp(&other.value))
38 }
39}
40
41impl Ord for EntityUri {
42 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
43 self.value.cmp(&other.value)
44 }
45}
46
47impl std::hash::Hash for EntityUri {
48 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
49 self.value.hash(state);
50 }
51}
52
53impl EntityUri {
54 pub fn as_str(&self) -> &str {
55 &self.value
56 }
57
58 #[allow(clippy::inherent_to_string_shadow_display)]
60 pub fn to_string(&self) -> String {
61 self.value.clone()
62 }
63
64 pub fn into_string(self) -> String {
65 self.value
66 }
67
68 pub fn kind(&self) -> &str {
69 &self.value[..self.name_start - 1]
70 }
71
72 pub fn namespace(&self) -> &str {
73 &self.value[..self.type_start - 1]
74 }
75
76 pub fn entity_type(&self) -> &str {
77 &self.value[self.type_start..self.version_start - 2]
78 }
79
80 pub fn version(&self) -> &str {
81 &self.value[self.version_start..self.name_start - 1]
82 }
83
84 pub fn name(&self) -> &str {
85 &self.value[self.name_start..]
86 }
87
88 pub fn new_kind_name(kind: &str, name: &str) -> Result<Self, EntityUriParseError> {
89 Self::parse(format!("{}:{}", kind, name))
91 }
92
93 pub fn parse(s: impl Into<String>) -> Result<Self, EntityUriParseError> {
94 let s = s.into();
95 let (kind, name) = s.split_once(':').ok_or_else(|| {
96 EntityUriParseError::new(s.clone(), EntityUriParseErrorKind::MissingKindNameSeparator)
97 })?;
98
99 let (ns, kind) = kind.split_once('/').ok_or_else(|| {
100 EntityUriParseError::new(
101 s.clone(),
102 EntityUriParseErrorKind::MissingKindNamespaceSeparator,
103 )
104 })?;
105
106 let (ty, version) = kind.split_once('.').ok_or_else(|| {
107 EntityUriParseError::new(s.clone(), EntityUriParseErrorKind::InvalidType)
108 })?;
109
110 if !is_valid_namespace(ns) {
111 return Err(EntityUriParseError::new(
112 s,
113 EntityUriParseErrorKind::InvalidNamespace,
114 ));
115 }
116 if !is_valid_type_name(ty) {
117 return Err(EntityUriParseError::new(
118 s,
119 EntityUriParseErrorKind::InvalidType,
120 ));
121 }
122 if !is_valid_version(version) {
123 return Err(EntityUriParseError::new(
124 s,
125 EntityUriParseErrorKind::InvalidTypeVersion,
126 ));
127 }
128 if !is_valid_name(name) {
129 return Err(EntityUriParseError::new(
130 s,
131 EntityUriParseErrorKind::InvalidName,
132 ));
133 }
134
135 let type_start = ns.len() + 1;
136 let version_start = type_start + ty.len() + 2;
137 let name_start = version_start + version.len();
138
139 Ok(Self {
140 value: s,
141 type_start,
142 version_start,
143 name_start,
144 })
145 }
146}
147
148impl std::fmt::Display for EntityUri {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 self.value.fmt(f)
151 }
152}
153
154fn is_valid_namespace(s: &str) -> bool {
155 s.split('.').all(|p| {
156 !p.is_empty()
157 && p.chars().all(|c| match c {
158 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' => true,
159 _ => false,
160 })
161 })
162}
163
164fn is_valid_type_name(s: &str) -> bool {
165 !s.is_empty()
166 && s.chars().all(|c| match c {
167 'a'..='z' | 'A'..='Z' | '0'..='9' => true,
168 _ => false,
169 })
170}
171
172fn is_valid_version(s: &str) -> bool {
173 !s.is_empty()
174 && s.chars().all(|c| match c {
175 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => true,
176 _ => false,
177 })
178}
179
180fn is_valid_name(name: &str) -> bool {
181 !name.is_empty()
182 && name.chars().all(|c| match c {
183 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' | '.' | '+' => true,
184 _ => false,
185 })
186}
187
188impl std::str::FromStr for EntityUri {
189 type Err = EntityUriParseError;
190
191 fn from_str(s: &str) -> Result<Self, Self::Err> {
192 Self::parse(s.to_string())
193 }
194}
195
196impl serde::Serialize for EntityUri {
197 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
198 serializer.serialize_str(&self.value)
199 }
200}
201
202impl<'de> serde::Deserialize<'de> for EntityUri {
203 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
204 let value = String::deserialize(deserializer)?;
205
206 Self::parse(value).map_err(serde::de::Error::custom)
207 }
208}
209
210impl schemars::JsonSchema for EntityUri {
211 fn schema_name() -> String {
212 "EntityUri".to_string()
213 }
214
215 fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
216 schemars::schema::Schema::Object(schemars::schema::SchemaObject {
217 instance_type: Some(schemars::schema::InstanceType::String.into()),
218 ..Default::default()
219 })
220 }
221}
222
223#[derive(Clone, Debug)]
224pub struct EntityUriParseError {
225 value: String,
226 kind: EntityUriParseErrorKind,
227}
228
229impl EntityUriParseError {
230 pub fn new(value: impl Into<String>, kind: EntityUriParseErrorKind) -> Self {
231 Self {
232 value: value.into(),
233 kind,
234 }
235 }
236}
237
238impl std::fmt::Display for EntityUriParseError {
239 fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
240 write!(_f, "invalid entity URI: '{}' ({:?})", self.value, self.kind)
241 }
242}
243
244impl std::error::Error for EntityUriParseError {}
245
246#[derive(Clone, Debug)]
247pub enum EntityUriParseErrorKind {
248 MissingKindNameSeparator,
249 MissingKindNamespaceSeparator,
250 InvalidNamespace,
251 InvalidType,
252 InvalidName,
253 InvalidTypeVersion,
254}
255
256impl std::fmt::Display for EntityUriParseErrorKind {
257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258 match self {
259 EntityUriParseErrorKind::MissingKindNameSeparator => {
260 write!(f, "missing kind/name separator")
261 }
262 EntityUriParseErrorKind::MissingKindNamespaceSeparator => {
263 write!(f, "missing kind/namespace separator")
264 }
265 EntityUriParseErrorKind::InvalidNamespace => write!(f, "invalid namespace"),
266 EntityUriParseErrorKind::InvalidType => write!(f, "invalid type"),
267 EntityUriParseErrorKind::InvalidName => write!(f, "invalid name"),
268 EntityUriParseErrorKind::InvalidTypeVersion => write!(f, "invalid type version"),
269 }
270 }
271}
272
273#[derive(
275 serde::Serialize, serde::Deserialize, schemars::JsonSchema, PartialEq, Eq, Clone, Debug,
276)]
277pub enum EntityOrRef<T> {
278 #[serde(rename = "ref")]
279 Ref(EntityUri),
280 #[serde(rename = "item")]
281 Item(T),
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn test_entity_uri_parse() {
290 let u = EntityUri::parse("my-ns.com/Ent.v1:lala123".to_string()).unwrap();
291 assert_eq!(u.namespace(), "my-ns.com");
292 assert_eq!(u.entity_type(), "Ent");
293 assert_eq!(u.version(), "1");
294 assert_eq!(u.name(), "lala123");
295 }
296}