norad/
identifier.rs

1use std::hash::Hash;
2use std::sync::Arc;
3
4use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
5
6use crate::error::ErrorKind;
7
8/// A [UFO Object Identifier][identifier].
9///
10/// Identifiers are optional attributes of several objects in the UFO.
11/// These identifiers are required to be unique within certain contexts
12/// as defined on a per object basis throughout this specification.
13/// Identifiers are specified as a string between one and 100 characters long.
14/// All characters must be in the printable ASCII range, 0x20 to 0x7E.
15///
16/// [identifier]: https://unifiedfontobject.org/versions/ufo3/conventions/#identifiers
17#[derive(Debug, Clone, Eq, Hash, PartialEq)]
18pub struct Identifier(Arc<str>);
19
20impl Identifier {
21    /// Create a new [`Identifier`] from a string, if it is valid.
22    ///
23    /// A valid identifier must have between 0 and 100 characters, and each
24    /// character must be in the printable ASCII range, 0x20 to 0x7E.
25    pub fn new(string: &str) -> Result<Self, ErrorKind> {
26        if is_valid_identifier(string) {
27            Ok(Identifier(string.into()))
28        } else {
29            Err(ErrorKind::BadIdentifier)
30        }
31    }
32
33    /// Creates a new `Identifier`, panicking if the given identifier is invalid.
34    #[cfg(test)]
35    pub(crate) fn new_raw(string: &str) -> Self {
36        assert!(is_valid_identifier(string));
37        Self(string.into())
38    }
39
40    /// Create a new [`Identifier`] from a UUID v4 identifier.
41    #[cfg(feature = "object-libs")]
42    pub fn from_uuidv4() -> Self {
43        Self::new(uuid::Uuid::new_v4().to_string().as_ref()).unwrap()
44    }
45
46    /// Return the raw identifier, as a `&str`.
47    pub fn as_str(&self) -> &str {
48        self.as_ref()
49    }
50}
51
52fn is_valid_identifier(s: &str) -> bool {
53    s.len() <= 100 && s.bytes().all(|b| (0x20..=0x7E).contains(&b))
54}
55
56impl AsRef<str> for Identifier {
57    fn as_ref(&self) -> &str {
58        self.0.as_ref()
59    }
60}
61
62impl std::ops::Deref for Identifier {
63    type Target = str;
64    fn deref(&self) -> &Self::Target {
65        self.0.as_ref()
66    }
67}
68
69// so that assert_eq! macros work
70impl<'a> PartialEq<&'a str> for Identifier {
71    fn eq(&self, other: &&'a str) -> bool {
72        self.0.as_ref() == *other
73    }
74}
75
76impl PartialEq<Identifier> for &str {
77    fn eq(&self, other: &Identifier) -> bool {
78        other == self
79    }
80}
81
82impl std::fmt::Display for Identifier {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
84        std::fmt::Display::fmt(&self.0, f)
85    }
86}
87
88impl std::borrow::Borrow<str> for Identifier {
89    fn borrow(&self) -> &str {
90        self.0.as_ref()
91    }
92}
93
94impl Serialize for Identifier {
95    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
96    where
97        S: Serializer,
98    {
99        debug_assert!(
100            is_valid_identifier(&self.0),
101            "all identifiers are validated on construction"
102        );
103        serializer.serialize_str(&self.0)
104    }
105}
106
107impl<'de> Deserialize<'de> for Identifier {
108    fn deserialize<D>(deserializer: D) -> Result<Identifier, D::Error>
109    where
110        D: Deserializer<'de>,
111    {
112        let string = String::deserialize(deserializer)?;
113        Identifier::new(string.as_str()).map_err(de::Error::custom)
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn identifier_parsing() {
123        let valid_chars = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
124        assert!(Identifier::new(valid_chars).is_ok());
125
126        let i2 = Identifier::new("0aAƤ");
127        assert!(i2.is_err());
128        let i3 = Identifier::new("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
129        assert!(i3.is_err());
130    }
131}