1
use std::{
2
    borrow::Cow,
3
    fmt::{Debug, Display, Write},
4
    sync::Arc,
5
};
6

            
7
use serde::{Deserialize, Serialize};
8

            
9
/// A schema name. Cloning is inexpensive.
10
137423472
#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, Ord, PartialOrd)]
11
#[serde(try_from = "String")]
12
#[serde(into = "String")]
13
pub struct Name {
14
    name: Arc<Cow<'static, str>>,
15
    needs_escaping: bool,
16
}
17

            
18
/// A name was unable to e parsed.
19
#[derive(thiserror::Error, Debug, Serialize, Deserialize, Clone)]
20
#[error("invalid name: {0}")]
21
pub struct InvalidNameError(pub String);
22

            
23
impl Name {
24
    /// Creates a new name.
25
12953476
    pub fn new<T: Into<Self>>(contents: T) -> Self {
26
12953476
        contents.into()
27
12953476
    }
28

            
29
    /// Parses a name that was previously encoded via [`Self::encoded()`].
30
    ///
31
    /// # Errors
32
    ///
33
    /// Returns [`InvalidNameError`] if the name contains invalid escape
34
    /// sequences.
35
387
    pub fn parse_encoded(encoded: &str) -> Result<Self, InvalidNameError> {
36
387
        let mut bytes = encoded.bytes();
37
387
        let mut decoded = Vec::with_capacity(encoded.len());
38
4087
        while let Some(byte) = bytes.next() {
39
3704
            if byte == b'_' {
40
142
                if let (Some(high), Some(low)) = (bytes.next(), bytes.next()) {
41
139
                    if let Some(byte) = hex_chars_to_byte(high, low) {
42
138
                        decoded.push(byte);
43
138
                        continue;
44
1
                    }
45
3
                }
46
4
                return Err(InvalidNameError(encoded.to_string()));
47
3562
            }
48
3562

            
49
3562
            decoded.push(byte);
50
        }
51

            
52
383
        String::from_utf8(decoded)
53
383
            .map(Self::from)
54
383
            .map_err(|_| InvalidNameError(encoded.to_string()))
55
387
    }
56

            
57
    /// Returns an encoded version of this name that contains only alphanumeric
58
    /// ASCII, underscore, and hyphen.
59
    #[must_use]
60
1
    pub fn encoded(&self) -> String {
61
1
        format!("{:#}", self)
62
1
    }
63
}
64

            
65
impl From<Cow<'static, str>> for Name {
66
80907813
    fn from(value: Cow<'static, str>) -> Self {
67
80907813
        let needs_escaping = !value
68
80907813
            .bytes()
69
686661398
            .all(|b| b.is_ascii_alphanumeric() || b == b'-');
70
80907813
        Self {
71
80907813
            name: Arc::new(value),
72
80907813
            needs_escaping,
73
80907813
        }
74
80907813
    }
75
}
76

            
77
impl From<&'static str> for Name {
78
44900836
    fn from(value: &'static str) -> Self {
79
44900836
        Self::from(Cow::Borrowed(value))
80
44900836
    }
81
}
82

            
83
impl From<String> for Name {
84
4090667
    fn from(value: String) -> Self {
85
4090667
        Self::from(Cow::Owned(value))
86
4090667
    }
87
}
88

            
89
#[allow(clippy::from_over_into)] // the auto into impl doesn't work with serde(into)
90
impl Into<String> for Name {
91
7664841
    fn into(self) -> String {
92
7664841
        self.name.to_string()
93
7664841
    }
94
}
95

            
96
impl Display for Name {
97
17668946
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98
17668946
        if f.alternate() && self.needs_escaping {
99
44960104
            for byte in self.name.bytes() {
100
44960104
                if byte.is_ascii_alphanumeric() || byte == b'-' {
101
39674635
                    f.write_char(byte as char)?;
102
                } else {
103
                    // Encode the byte as _FF
104
5292273
                    f.write_char('_')?;
105
5292273
                    f.write_char(nibble_to_hex_char(byte >> 4))?;
106
5292273
                    f.write_char(nibble_to_hex_char(byte & 0xF))?;
107
                }
108
            }
109
5292140
            Ok(())
110
        } else {
111
12378534
            Display::fmt(&self.name, f)
112
        }
113
17670674
    }
114
}
115

            
116
10584060
const fn nibble_to_hex_char(nibble: u8) -> char {
117
10584438
    let ch = match nibble {
118
10584087
        0..=9 => b'0' + nibble,
119
5292214
        _ => b'a' + nibble - 10,
120
    };
121
10584438
    ch as char
122
10584438
}
123

            
124
const fn hex_chars_to_byte(high_nibble: u8, low_nibble: u8) -> Option<u8> {
125
    match (
126
139
        hex_char_to_nibble(high_nibble),
127
139
        hex_char_to_nibble(low_nibble),
128
    ) {
129
138
        (Some(high_nibble), Some(low_nibble)) => Some(high_nibble << 4 | low_nibble),
130
1
        _ => None,
131
    }
132
139
}
133

            
134
278
const fn hex_char_to_nibble(nibble: u8) -> Option<u8> {
135
278
    let ch = match nibble {
136
278
        b'0'..=b'9' => nibble - b'0',
137
134
        b'a'..=b'f' => nibble - b'a' + 10,
138
1
        _ => return None,
139
    };
140
277
    Some(ch)
141
278
}
142

            
143
impl AsRef<str> for Name {
144
321468
    fn as_ref(&self) -> &str {
145
321468
        self.name.as_ref()
146
321468
    }
147
}
148

            
149
/// The owner of a schema item. This should represent the company, group, or
150
/// individual that created the item in question. This value is used for
151
/// namespacing. Changing this after values are in use is not supported without
152
/// manual migrations at this time.
153
59188144
#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, Ord, PartialOrd)]
154
#[serde(transparent)]
155
pub struct Authority(Name);
156

            
157
impl From<Cow<'static, str>> for Authority {
158
31950276
    fn from(value: Cow<'static, str>) -> Self {
159
31950276
        Self::from(Name::from(value))
160
31950276
    }
161
}
162

            
163
impl From<&'static str> for Authority {
164
31950249
    fn from(value: &'static str) -> Self {
165
31950249
        Self::from(Cow::Borrowed(value))
166
31950249
    }
167
}
168

            
169
impl From<String> for Authority {
170
    fn from(value: String) -> Self {
171
        Self::from(Cow::Owned(value))
172
    }
173
}
174

            
175
impl From<Name> for Authority {
176
31954274
    fn from(value: Name) -> Self {
177
31954274
        Self(value)
178
31954274
    }
179
}
180

            
181
impl Display for Authority {
182
6967813
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183
6967813
        Display::fmt(&self.0, f)
184
6967813
    }
185
}
186

            
187
/// The name of a [`Schema`](super::Schema).
188
1390473
#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, Ord, PartialOrd)]
189
pub struct SchemaName {
190
    /// The authority of this schema.
191
    pub authority: Authority,
192

            
193
    /// The name of this schema.
194
    pub name: Name,
195
}
196

            
197
impl SchemaName {
198
    /// Creates a name for a [`Schema`](super::Schema) that is not meant
199
    /// to be shared with other developers.
200
842562
    pub fn private<N: Into<Name>>(name: N) -> Self {
201
842562
        let authority = Authority::from("private");
202
842562
        let name = name.into();
203
842562
        Self { authority, name }
204
842562
    }
205

            
206
    /// Creates a new schema name.
207
665848
    pub fn new<A: Into<Authority>, N: Into<Name>>(authority: A, name: N) -> Self {
208
665848
        let authority = authority.into();
209
665848
        let name = name.into();
210
665848
        Self { authority, name }
211
665848
    }
212

            
213
    /// Parses a schema name that was previously encoded via
214
    /// [`Self::encoded()`].
215
    ///
216
    /// # Errors
217
    ///
218
    /// Returns [`InvalidNameError`] if the name contains invalid escape
219
    /// sequences or contains more than two periods.
220
190
    pub fn parse_encoded(schema_name: &str) -> Result<Self, InvalidNameError> {
221
190
        let mut parts = schema_name.split('.');
222
190
        if let (Some(authority), Some(name), None) = (parts.next(), parts.next(), parts.next()) {
223
190
            let authority = Name::parse_encoded(authority)?;
224
190
            let name = Name::parse_encoded(name)?;
225

            
226
190
            Ok(Self::new(authority, name))
227
        } else {
228
            Err(InvalidNameError(schema_name.to_string()))
229
        }
230
190
    }
231

            
232
    /// Encodes this schema name such that the authority and name can be
233
    /// safely parsed using [`Self::parse_encoded`].
234
    #[must_use]
235
1135
    pub fn encoded(&self) -> String {
236
1135
        format!("{:#}", self)
237
1135
    }
238
}
239

            
240
impl Display for SchemaName {
241
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242
1136
        Display::fmt(&self.authority, f)?;
243
1136
        f.write_char('.')?;
244
1136
        Display::fmt(&self.name, f)
245
1136
    }
246
}
247

            
248
/// The namespaced name of a [`Collection`](super::Collection).
249
57746803
#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone)]
250
pub struct CollectionName {
251
    /// The authority of this collection. This name is used to ensure
252
    /// collections from multiple authors/authorities can be used in the same
253
    /// schema.
254
    pub authority: Authority,
255

            
256
    /// The name of this collection. Must be unique within the [`Schema`](super::Schema)
257
    pub name: Name,
258
}
259

            
260
impl CollectionName {
261
    /// Creates a name for a [`Collection`](super::Collection) that is not meant
262
    /// to be shared with other developers.
263
2185
    pub fn private<N: Into<Name>>(name: N) -> Self {
264
2185
        let authority = Authority::from("private");
265
2185
        let name = name.into();
266
2185
        Self { authority, name }
267
2185
    }
268

            
269
    /// Creates a new collection name.
270
30384167
    pub fn new<A: Into<Authority>, N: Into<Name>>(authority: A, name: N) -> Self {
271
30384167
        let authority = authority.into();
272
30384167
        let name = name.into();
273
30384167
        Self { authority, name }
274
30384167
    }
275

            
276
    /// Parses a colleciton name that was previously encoded via
277
    /// [`Self::encoded()`].
278
    ///
279
    /// # Errors
280
    ///
281
    /// Returns [`InvalidNameError`] if the name contains invalid escape
282
    /// sequences or contains more than two periods.
283
1
    pub fn parse_encoded(collection_name: &str) -> Result<Self, InvalidNameError> {
284
1
        let mut parts = collection_name.split('.');
285
1
        if let (Some(authority), Some(name), None) = (parts.next(), parts.next(), parts.next()) {
286
1
            let authority = Name::parse_encoded(authority)?;
287
1
            let name = Name::parse_encoded(name)?;
288

            
289
1
            Ok(Self::new(authority, name))
290
        } else {
291
            Err(InvalidNameError(collection_name.to_string()))
292
        }
293
1
    }
294

            
295
    /// Encodes this collection name such that the authority and name can be
296
    /// safely parsed using [`Self::parse_encoded`].
297
    #[must_use]
298
622
    pub fn encoded(&self) -> String {
299
622
        format!("{:#}", self)
300
622
    }
301
}
302

            
303
impl Display for CollectionName {
304
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305
6966812
        Display::fmt(&self.authority, f)?;
306
6966812
        f.write_char('.')?;
307
6966947
        Display::fmt(&self.name, f)
308
6966947
    }
309
}
310

            
311
/// The name of a [`View`](super::View).
312
19065517
#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone)]
313
pub struct ViewName {
314
    /// The name of the collection that contains this view.
315
    pub collection: CollectionName,
316
    /// The name of this view.
317
    pub name: Name,
318
}
319

            
320
impl ViewName {
321
    /// Creates a new view name.
322
    pub fn new<
323
        C: TryInto<CollectionName, Error = InvalidNameError>,
324
        N: TryInto<Name, Error = InvalidNameError>,
325
    >(
326
        collection: C,
327
        name: N,
328
    ) -> Result<Self, InvalidNameError> {
329
        let collection = collection.try_into()?;
330
        let name = name.try_into()?;
331
        Ok(Self { collection, name })
332
    }
333
}
334

            
335
impl Display for ViewName {
336
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337
3735909
        Display::fmt(&self.collection, f)?;
338
3735909
        f.write_char('.')?;
339
3735963
        Display::fmt(&self.name, f)
340
3735963
    }
341
}
342

            
343
1
#[test]
344
1
fn name_escaping_tests() {
345
1
    const VALID_CHARS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
346
1
    const INVALID_CHARS: &str = "._hello\u{1F680}";
347
1
    const ESCAPED_INVALID: &str = "_2e_5fhello_f0_9f_9a_80";
348
1
    assert_eq!(Name::new(VALID_CHARS).to_string(), VALID_CHARS);
349
1
    assert_eq!(Name::new(INVALID_CHARS).to_string(), INVALID_CHARS);
350
1
    assert_eq!(Name::new(INVALID_CHARS).encoded(), ESCAPED_INVALID);
351
1
    assert_eq!(
352
1
        Name::parse_encoded(ESCAPED_INVALID).unwrap(),
353
1
        Name::new(INVALID_CHARS)
354
1
    );
355
1
    Name::parse_encoded("_").unwrap_err();
356
1
    Name::parse_encoded("_0").unwrap_err();
357
1
    Name::parse_encoded("_z").unwrap_err();
358
1
    Name::parse_encoded("_0z").unwrap_err();
359
1
}
360

            
361
1
#[test]
362
1
fn joined_names_tests() {
363
1
    const INVALID_CHARS: &str = "._hello\u{1F680}.._world\u{1F680}";
364
1
    const ESCAPED_INVALID: &str = "_2e_5fhello_f0_9f_9a_80._2e_5fworld_f0_9f_9a_80";
365
1
    let collection = CollectionName::parse_encoded(ESCAPED_INVALID).unwrap();
366
1
    assert_eq!(collection.to_string(), INVALID_CHARS);
367
1
    assert_eq!(collection.encoded(), ESCAPED_INVALID);
368

            
369
1
    let schema_name = SchemaName::parse_encoded(ESCAPED_INVALID).unwrap();
370
1
    assert_eq!(schema_name.to_string(), INVALID_CHARS);
371
1
    assert_eq!(schema_name.encoded(), ESCAPED_INVALID);
372
1
}