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
129365399
#[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
12347956
    pub fn new<T: Into<Self>>(contents: T) -> Self {
26
12347956
        contents.into()
27
12347956
    }
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
373
    pub fn parse_encoded(encoded: &str) -> Result<Self, InvalidNameError> {
36
373
        let mut bytes = encoded.bytes();
37
373
        let mut decoded = Vec::with_capacity(encoded.len());
38
3938
        while let Some(byte) = bytes.next() {
39
3569
            if byte == b'_' {
40
138
                if let (Some(high), Some(low)) = (bytes.next(), bytes.next()) {
41
135
                    if let Some(byte) = hex_chars_to_byte(high, low) {
42
134
                        decoded.push(byte);
43
134
                        continue;
44
1
                    }
45
3
                }
46
4
                return Err(InvalidNameError(encoded.to_string()));
47
3431
            }
48
3431

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

            
52
369
        String::from_utf8(decoded)
53
369
            .map(Self::from)
54
369
            .map_err(|_| InvalidNameError(encoded.to_string()))
55
373
    }
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
76979697
    fn from(value: Cow<'static, str>) -> Self {
67
76979697
        let needs_escaping = !value
68
76979697
            .bytes()
69
652852887
            .all(|b| b.is_ascii_alphanumeric() || b == b'-');
70
76979697
        Self {
71
76979697
            name: Arc::new(value),
72
76979697
            needs_escaping,
73
76979697
        }
74
76979697
    }
75
}
76

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

            
83
impl From<String> for Name {
84
3871301
    fn from(value: String) -> Self {
85
3871301
        Self::from(Cow::Owned(value))
86
3871301
    }
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
7646340
    fn into(self) -> String {
92
7646340
        self.name.to_string()
93
7646340
    }
94
}
95

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

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

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

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

            
143
impl AsRef<str> for Name {
144
301782
    fn as_ref(&self) -> &str {
145
301782
        self.name.as_ref()
146
301782
    }
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
55593499
#[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
30397705
    fn from(value: Cow<'static, str>) -> Self {
159
30397705
        Self::from(Name::from(value))
160
30397705
    }
161
}
162

            
163
impl From<&'static str> for Authority {
164
30397549
    fn from(value: &'static str) -> Self {
165
30397549
        Self::from(Cow::Borrowed(value))
166
30397549
    }
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
30401243
    fn from(value: Name) -> Self {
177
30401243
        Self(value)
178
30401243
    }
179
}
180

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

            
187
/// The name of a [`Schema`](super::Schema).
188
1322705
#[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
809458
    pub fn private<N: Into<Name>>(name: N) -> Self {
201
809458
        let authority = Authority::from("private");
202
809458
        let name = name.into();
203
809458
        Self { authority, name }
204
809458
    }
205

            
206
    /// Creates a new schema name.
207
625119
    pub fn new<A: Into<Authority>, N: Into<Name>>(authority: A, name: N) -> Self {
208
625119
        let authority = authority.into();
209
625119
        let name = name.into();
210
625119
        Self { authority, name }
211
625119
    }
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
183
    pub fn parse_encoded(schema_name: &str) -> Result<Self, InvalidNameError> {
221
183
        let mut parts = schema_name.split('.');
222
183
        if let (Some(authority), Some(name), None) = (parts.next(), parts.next(), parts.next()) {
223
183
            let authority = Name::parse_encoded(authority)?;
224
183
            let name = Name::parse_encoded(name)?;
225

            
226
183
            Ok(Self::new(authority, name))
227
        } else {
228
            Err(InvalidNameError(schema_name.to_string()))
229
        }
230
183
    }
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
1093
    pub fn encoded(&self) -> String {
236
1093
        format!("{:#}", self)
237
1093
    }
238
}
239

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

            
248
/// The namespaced name of a [`Collection`](super::Collection).
249
54222675
#[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
1537
    pub fn private<N: Into<Name>>(name: N) -> Self {
264
1537
        let authority = Authority::from("private");
265
1537
        let name = name.into();
266
1537
        Self { authority, name }
267
1537
    }
268

            
269
    /// Creates a new collection name.
270
28924442
    pub fn new<A: Into<Authority>, N: Into<Name>>(authority: A, name: N) -> Self {
271
28924442
        let authority = authority.into();
272
28924442
        let name = name.into();
273
28924442
        Self { authority, name }
274
28924442
    }
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
599
    pub fn encoded(&self) -> String {
299
599
        format!("{:#}", self)
300
599
    }
301
}
302

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

            
311
/// The name of a [`View`](super::View).
312
18202165
#[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
3863080
        Display::fmt(&self.collection, f)?;
338
3863080
        f.write_char('.')?;
339
3863080
        Display::fmt(&self.name, f)
340
3863080
    }
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
}