1use std::borrow::Cow;
2use std::fmt::{Debug, Display, Write};
3use std::ops::Deref;
4use std::str::FromStr;
5use std::sync::Arc;
6
7use serde::{Deserialize, Serialize};
8
9#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, Ord, PartialOrd)]
11#[serde(try_from = "String")]
12#[serde(into = "String")]
13pub struct Name {
14 name: Arc<Cow<'static, str>>,
15 needs_escaping: bool,
16}
17
18#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)]
20#[error("invalid name: {0}")]
21pub struct InvalidNameError(pub String);
22
23impl Name {
24 pub fn new<T: Into<Self>>(contents: T) -> Self {
26 contents.into()
27 }
28
29 pub fn parse_encoded(encoded: &str) -> Result<Self, InvalidNameError> {
36 let mut bytes = encoded.bytes();
37 let mut decoded = Vec::with_capacity(encoded.len());
38 while let Some(byte) = bytes.next() {
39 if byte == b'_' {
40 if let (Some(high), Some(low)) = (bytes.next(), bytes.next()) {
41 if let Some(byte) = hex_chars_to_byte(high, low) {
42 decoded.push(byte);
43 continue;
44 }
45 }
46 return Err(InvalidNameError(encoded.to_string()));
47 }
48
49 decoded.push(byte);
50 }
51
52 String::from_utf8(decoded)
53 .map(Self::from)
54 .map_err(|_| InvalidNameError(encoded.to_string()))
55 }
56
57 #[must_use]
60 pub fn encoded(&self) -> String {
61 format!("{self:#}")
62 }
63}
64
65impl From<Cow<'static, str>> for Name {
66 fn from(value: Cow<'static, str>) -> Self {
67 let needs_escaping = !value
68 .bytes()
69 .all(|b| b.is_ascii_alphanumeric() || b == b'-');
70 Self {
71 name: Arc::new(value),
72 needs_escaping,
73 }
74 }
75}
76
77impl From<&'static str> for Name {
78 fn from(value: &'static str) -> Self {
79 Self::from(Cow::Borrowed(value))
80 }
81}
82
83impl From<String> for Name {
84 fn from(value: String) -> Self {
85 Self::from(Cow::Owned(value))
86 }
87}
88
89#[allow(clippy::from_over_into)] impl Into<String> for Name {
91 fn into(self) -> String {
92 self.name.to_string()
93 }
94}
95
96impl Display for Name {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 if f.alternate() && self.needs_escaping {
99 for byte in self.name.bytes() {
100 if byte.is_ascii_alphanumeric() || byte == b'-' {
101 f.write_char(byte as char)?;
102 } else {
103 f.write_char('_')?;
105 f.write_char(nibble_to_hex_char(byte >> 4))?;
106 f.write_char(nibble_to_hex_char(byte & 0xF))?;
107 }
108 }
109 Ok(())
110 } else {
111 Display::fmt(&self.name, f)
112 }
113 }
114}
115
116const fn nibble_to_hex_char(nibble: u8) -> char {
117 let ch = match nibble {
118 0..=9 => b'0' + nibble,
119 _ => b'a' + nibble - 10,
120 };
121 ch as char
122}
123
124const fn hex_chars_to_byte(high_nibble: u8, low_nibble: u8) -> Option<u8> {
125 match (
126 hex_char_to_nibble(high_nibble),
127 hex_char_to_nibble(low_nibble),
128 ) {
129 (Some(high_nibble), Some(low_nibble)) => Some(high_nibble << 4 | low_nibble),
130 _ => None,
131 }
132}
133
134const fn hex_char_to_nibble(nibble: u8) -> Option<u8> {
135 let ch = match nibble {
136 b'0'..=b'9' => nibble - b'0',
137 b'a'..=b'f' => nibble - b'a' + 10,
138 _ => return None,
139 };
140 Some(ch)
141}
142
143impl AsRef<str> for Name {
144 fn as_ref(&self) -> &str {
145 self.name.as_ref()
146 }
147}
148
149#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, Ord, PartialOrd)]
154#[serde(transparent)]
155pub struct Authority(Name);
156
157impl From<Cow<'static, str>> for Authority {
158 fn from(value: Cow<'static, str>) -> Self {
159 Self::from(Name::from(value))
160 }
161}
162
163impl From<&'static str> for Authority {
164 fn from(value: &'static str) -> Self {
165 Self::from(Cow::Borrowed(value))
166 }
167}
168
169impl From<String> for Authority {
170 fn from(value: String) -> Self {
171 Self::from(Cow::Owned(value))
172 }
173}
174
175impl From<Name> for Authority {
176 fn from(value: Name) -> Self {
177 Self(value)
178 }
179}
180
181impl Display for Authority {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 Display::fmt(&self.0, f)
184 }
185}
186
187impl AsRef<str> for Authority {
188 fn as_ref(&self) -> &str {
189 self.0.as_ref()
190 }
191}
192
193#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, Ord, PartialOrd)]
195pub struct QualifiedName {
196 pub authority: Authority,
198
199 pub name: Name,
201}
202
203impl Display for QualifiedName {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 Display::fmt(&self.authority, f)?;
206 f.write_char('.')?;
207 Display::fmt(&self.name, f)
208 }
209}
210
211impl FromStr for QualifiedName {
212 type Err = InvalidNameError;
213
214 fn from_str(s: &str) -> Result<Self, Self::Err> {
215 Self::parse_encoded(s)
216 }
217}
218
219pub trait Qualified: Display + Sized {
221 #[must_use]
224 fn private<N: Into<Name>>(name: N) -> Self {
225 Self::new(Authority::from("private"), name)
226 }
227
228 #[must_use]
230 fn new<A: Into<Authority>, N: Into<Name>>(authority: A, name: N) -> Self;
231
232 fn parse_encoded(schema_name: &str) -> Result<Self, InvalidNameError> {
240 let mut parts = schema_name.split('.');
241 if let (Some(authority), Some(name), None) = (parts.next(), parts.next(), parts.next()) {
242 let authority = Name::parse_encoded(authority)?;
243 let name = Name::parse_encoded(name)?;
244
245 Ok(Self::new(authority, name))
246 } else {
247 Err(InvalidNameError(schema_name.to_string()))
248 }
249 }
250
251 #[must_use]
254 fn encoded(&self) -> String {
255 format!("{self:#}")
256 }
257}
258
259impl Qualified for QualifiedName {
260 fn new<A: Into<Authority>, N: Into<Name>>(authority: A, name: N) -> Self {
261 Self {
262 authority: authority.into(),
263 name: name.into(),
264 }
265 }
266}
267
268#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, Ord, PartialOrd)]
270#[serde(transparent)]
271pub struct SchemaName(pub(crate) QualifiedName);
272
273impl Qualified for SchemaName {
274 fn new<A: Into<Authority>, N: Into<Name>>(authority: A, name: N) -> Self {
275 Self(QualifiedName::new(authority, name))
276 }
277}
278
279impl Display for SchemaName {
280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281 Display::fmt(&self.0, f)
282 }
283}
284
285impl Deref for SchemaName {
286 type Target = QualifiedName;
287
288 fn deref(&self) -> &Self::Target {
289 &self.0
290 }
291}
292
293impl From<CollectionName> for SchemaName {
294 fn from(name: CollectionName) -> Self {
295 Self(name.0)
296 }
297}
298
299impl FromStr for SchemaName {
300 type Err = InvalidNameError;
301
302 fn from_str(s: &str) -> Result<Self, Self::Err> {
303 Self::parse_encoded(s)
304 }
305}
306
307#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, Ord, PartialOrd)]
309#[serde(transparent)]
310pub struct CollectionName(pub(crate) QualifiedName);
311
312impl Qualified for CollectionName {
313 fn new<A: Into<Authority>, N: Into<Name>>(authority: A, name: N) -> Self {
314 Self(QualifiedName::new(authority, name))
315 }
316}
317
318impl Display for CollectionName {
319 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320 Display::fmt(&self.0, f)
321 }
322}
323
324impl Deref for CollectionName {
325 type Target = QualifiedName;
326
327 fn deref(&self) -> &Self::Target {
328 &self.0
329 }
330}
331
332impl FromStr for CollectionName {
333 type Err = InvalidNameError;
334
335 fn from_str(s: &str) -> Result<Self, Self::Err> {
336 Self::parse_encoded(s)
337 }
338}
339
340#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, PartialOrd, Ord)]
342pub struct ViewName {
343 pub collection: CollectionName,
345 pub name: Name,
347}
348
349impl ViewName {
350 pub fn new<
352 C: TryInto<CollectionName, Error = InvalidNameError>,
353 N: TryInto<Name, Error = InvalidNameError>,
354 >(
355 collection: C,
356 name: N,
357 ) -> Result<Self, InvalidNameError> {
358 let collection = collection.try_into()?;
359 let name = name.try_into()?;
360 Ok(Self { collection, name })
361 }
362}
363
364impl Display for ViewName {
365 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366 Display::fmt(&self.collection, f)?;
367 f.write_char('.')?;
368 Display::fmt(&self.name, f)
369 }
370}
371
372impl FromStr for ViewName {
373 type Err = InvalidNameError;
374
375 fn from_str(s: &str) -> Result<Self, Self::Err> {
376 let (first, view_name) = s
377 .rsplit_once('.')
378 .ok_or_else(|| InvalidNameError(s.to_string()))?;
379
380 let collection = first.parse()?;
381 let name = Name::parse_encoded(view_name)?;
382 Ok(Self { collection, name })
383 }
384}
385
386#[test]
387fn name_escaping_tests() {
388 const VALID_CHARS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
389 const INVALID_CHARS: &str = "._hello\u{1F680}";
390 const ESCAPED_INVALID: &str = "_2e_5fhello_f0_9f_9a_80";
391 assert_eq!(Name::new(VALID_CHARS).to_string(), VALID_CHARS);
392 assert_eq!(Name::new(INVALID_CHARS).to_string(), INVALID_CHARS);
393 assert_eq!(Name::new(INVALID_CHARS).encoded(), ESCAPED_INVALID);
394 assert_eq!(
395 Name::parse_encoded(ESCAPED_INVALID).unwrap(),
396 Name::new(INVALID_CHARS)
397 );
398 Name::parse_encoded("_").unwrap_err();
399 Name::parse_encoded("_0").unwrap_err();
400 Name::parse_encoded("_z").unwrap_err();
401 Name::parse_encoded("_0z").unwrap_err();
402}
403
404#[test]
405fn joined_names_tests() {
406 const INVALID_CHARS: &str = "._hello\u{1F680}.._world\u{1F680}";
407 const ESCAPED_INVALID: &str = "_2e_5fhello_f0_9f_9a_80._2e_5fworld_f0_9f_9a_80";
408 let collection = CollectionName::parse_encoded(ESCAPED_INVALID).unwrap();
409 assert_eq!(collection.to_string(), INVALID_CHARS);
410 assert_eq!(collection.encoded(), ESCAPED_INVALID);
411
412 let schema_name = SchemaName::parse_encoded(ESCAPED_INVALID).unwrap();
413 assert_eq!(schema_name.to_string(), INVALID_CHARS);
414 assert_eq!(schema_name.encoded(), ESCAPED_INVALID);
415}