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

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

            
9
/// A schema name. Cloning is inexpensive.
10
346207620
#[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
34019332
    pub fn new<T: Into<Self>>(contents: T) -> Self {
26
34019332
        contents.into()
27
34019332
    }
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
569
    pub fn parse_encoded(encoded: &str) -> Result<Self, InvalidNameError> {
36
569
        let mut bytes = encoded.bytes();
37
569
        let mut decoded = Vec::with_capacity(encoded.len());
38
6024
        while let Some(byte) = bytes.next() {
39
5459
            if byte == b'_' {
40
194
                if let (Some(high), Some(low)) = (bytes.next(), bytes.next()) {
41
191
                    if let Some(byte) = hex_chars_to_byte(high, low) {
42
190
                        decoded.push(byte);
43
190
                        continue;
44
1
                    }
45
3
                }
46
4
                return Err(InvalidNameError(encoded.to_string()));
47
5265
            }
48
5265

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

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

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

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

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

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

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

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

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

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

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

            
187
impl AsRef<str> for Authority {
188
    fn as_ref(&self) -> &str {
189
        self.0.as_ref()
190
    }
191
}
192

            
193
/// A namespaced name.
194
142838143
#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, Ord, PartialOrd)]
195
pub struct QualifiedName {
196
    /// The authority that defines this name.
197
    pub authority: Authority,
198

            
199
    /// The name, unique within `authority`.
200
    pub name: Name,
201
}
202

            
203
impl Display for QualifiedName {
204
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205
18775684
        Display::fmt(&self.authority, f)?;
206
18775684
        f.write_char('.')?;
207
18775684
        Display::fmt(&self.name, f)
208
18775684
    }
209
}
210

            
211
impl 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

            
219
/// Functions for creating qualified names
220
pub trait Qualified: Display + Sized {
221
    /// Creates a name that is not meant to be shared with other developers or
222
    /// projects.
223
    #[must_use]
224
1730397
    fn private<N: Into<Name>>(name: N) -> Self {
225
1730397
        Self::new(Authority::from("private"), name)
226
1730397
    }
227

            
228
    /// Creates a new qualified name.
229
    #[must_use]
230
    fn new<A: Into<Authority>, N: Into<Name>>(authority: A, name: N) -> Self;
231

            
232
    /// Parses a schema name that was previously encoded via
233
    /// [`Self::encoded()`].
234
    ///
235
    /// # Errors
236
    ///
237
    /// Returns [`InvalidNameError`] if the name contains invalid escape
238
    /// sequences or contains more than two periods.
239
282
    fn parse_encoded(schema_name: &str) -> Result<Self, InvalidNameError> {
240
282
        let mut parts = schema_name.split('.');
241
282
        if let (Some(authority), Some(name), None) = (parts.next(), parts.next(), parts.next()) {
242
282
            let authority = Name::parse_encoded(authority)?;
243
282
            let name = Name::parse_encoded(name)?;
244

            
245
282
            Ok(Self::new(authority, name))
246
        } else {
247
            Err(InvalidNameError(schema_name.to_string()))
248
        }
249
282
    }
250

            
251
    /// Encodes this schema name such that the authority and name can be
252
    /// safely parsed using [`Self::parse_encoded`].
253
    #[must_use]
254
1368
    fn encoded(&self) -> String {
255
1368
        format!("{self:#}")
256
1368
    }
257
}
258

            
259
impl Qualified for QualifiedName {
260
80354941
    fn new<A: Into<Authority>, N: Into<Name>>(authority: A, name: N) -> Self {
261
80354941
        Self {
262
80354941
            authority: authority.into(),
263
80354941
            name: name.into(),
264
80354941
        }
265
80354941
    }
266
}
267

            
268
/// The name of a [`Schema`](super::Schema).
269
2691287
#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, Ord, PartialOrd)]
270
#[serde(transparent)]
271
pub struct SchemaName(pub(crate) QualifiedName);
272

            
273
impl Qualified for SchemaName {
274
3155681
    fn new<A: Into<Authority>, N: Into<Name>>(authority: A, name: N) -> Self {
275
3155681
        Self(QualifiedName::new(authority, name))
276
3155681
    }
277
}
278

            
279
impl Display for SchemaName {
280
1762
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281
1762
        Display::fmt(&self.0, f)
282
1762
    }
283
}
284

            
285
impl Deref for SchemaName {
286
    type Target = QualifiedName;
287

            
288
    fn deref(&self) -> &Self::Target {
289
        &self.0
290
    }
291
}
292

            
293
impl From<CollectionName> for SchemaName {
294
16241
    fn from(name: CollectionName) -> Self {
295
16241
        Self(name.0)
296
16241
    }
297
}
298

            
299
impl 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
/// The namespaced name of a [`Collection`](super::Collection).
308
136592663
#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, Ord, PartialOrd)]
309
#[serde(transparent)]
310
pub struct CollectionName(pub(crate) QualifiedName);
311

            
312
impl Qualified for CollectionName {
313
74043550
    fn new<A: Into<Authority>, N: Into<Name>>(authority: A, name: N) -> Self {
314
74043550
        Self(QualifiedName::new(authority, name))
315
74043550
    }
316
}
317

            
318
impl Display for CollectionName {
319
18773922
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320
18773922
        Display::fmt(&self.0, f)
321
18773922
    }
322
}
323

            
324
impl Deref for CollectionName {
325
    type Target = QualifiedName;
326

            
327
6
    fn deref(&self) -> &Self::Target {
328
6
        &self.0
329
6
    }
330
}
331

            
332
impl 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
/// The name of a [`View`](super::View).
341
60531334
#[derive(Hash, PartialEq, Eq, Deserialize, Serialize, Debug, Clone, PartialOrd, Ord)]
342
pub struct ViewName {
343
    /// The name of the collection that contains this view.
344
    pub collection: CollectionName,
345
    /// The name of this view.
346
    pub name: Name,
347
}
348

            
349
impl ViewName {
350
    /// Creates a new view name.
351
    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

            
364
impl Display for ViewName {
365
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366
10274720
        Display::fmt(&self.collection, f)?;
367
10274720
        f.write_char('.')?;
368
10274720
        Display::fmt(&self.name, f)
369
10274720
    }
370
}
371

            
372
impl 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
1
#[test]
387
1
fn name_escaping_tests() {
388
1
    const VALID_CHARS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
389
1
    const INVALID_CHARS: &str = "._hello\u{1F680}";
390
1
    const ESCAPED_INVALID: &str = "_2e_5fhello_f0_9f_9a_80";
391
1
    assert_eq!(Name::new(VALID_CHARS).to_string(), VALID_CHARS);
392
1
    assert_eq!(Name::new(INVALID_CHARS).to_string(), INVALID_CHARS);
393
1
    assert_eq!(Name::new(INVALID_CHARS).encoded(), ESCAPED_INVALID);
394
1
    assert_eq!(
395
1
        Name::parse_encoded(ESCAPED_INVALID).unwrap(),
396
1
        Name::new(INVALID_CHARS)
397
1
    );
398
1
    Name::parse_encoded("_").unwrap_err();
399
1
    Name::parse_encoded("_0").unwrap_err();
400
1
    Name::parse_encoded("_z").unwrap_err();
401
1
    Name::parse_encoded("_0z").unwrap_err();
402
1
}
403

            
404
1
#[test]
405
1
fn joined_names_tests() {
406
1
    const INVALID_CHARS: &str = "._hello\u{1F680}.._world\u{1F680}";
407
1
    const ESCAPED_INVALID: &str = "_2e_5fhello_f0_9f_9a_80._2e_5fworld_f0_9f_9a_80";
408
1
    let collection = CollectionName::parse_encoded(ESCAPED_INVALID).unwrap();
409
1
    assert_eq!(collection.to_string(), INVALID_CHARS);
410
1
    assert_eq!(collection.encoded(), ESCAPED_INVALID);
411

            
412
1
    let schema_name = SchemaName::parse_encoded(ESCAPED_INVALID).unwrap();
413
1
    assert_eq!(schema_name.to_string(), INVALID_CHARS);
414
1
    assert_eq!(schema_name.encoded(), ESCAPED_INVALID);
415
1
}