indradb/models/
identifiers.rs

1use std::convert::TryFrom;
2use std::ops::Deref;
3use std::str::FromStr;
4
5use crate::errors::{ValidationError, ValidationResult};
6
7use internment::Intern;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10/// A string that must be less than 256 characters long, and can only contain
11/// letters, numbers, dashes and underscores. This is used for vertex and edge
12/// types, as well as property names.
13#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash, Ord, PartialOrd)]
14pub struct Identifier(pub(crate) Intern<String>);
15
16impl Identifier {
17    /// Constructs a new identifier.
18    ///
19    /// # Arguments
20    /// * `s`: The identifier value.
21    ///
22    /// # Errors
23    /// Returns a `ValidationError` if the identifier is longer than 255
24    /// characters, or has invalid characters.
25    pub fn new<S: Into<String>>(s: S) -> ValidationResult<Self> {
26        let s = s.into();
27
28        if s.len() > 255 {
29            Err(ValidationError::ValueTooLong)
30        } else if !s.chars().all(|c| c == '-' || c == '_' || c.is_alphanumeric()) {
31            Err(ValidationError::InvalidValue)
32        } else {
33            Ok(Self(Intern::new(s)))
34        }
35    }
36
37    /// Constructs a new identifier, without any checks that it is valid.
38    ///
39    /// # Arguments
40    /// * `s`: The identifier value.
41    ///
42    /// # Safety
43    /// This function is marked unsafe because there's no verification that
44    /// the identifier is valid.
45    pub unsafe fn new_unchecked<S: Into<String>>(s: S) -> Self {
46        Self(Intern::new(s.into()))
47    }
48
49    /// Gets a reference to the identifier value.
50    pub fn as_str(&self) -> &str {
51        &self.0
52    }
53}
54
55impl Default for Identifier {
56    fn default() -> Self {
57        Self(Intern::new("".to_string()))
58    }
59}
60
61impl Deref for Identifier {
62    type Target = String;
63    fn deref(&self) -> &Self::Target {
64        &self.0
65    }
66}
67
68impl FromStr for Identifier {
69    type Err = ValidationError;
70
71    fn from_str(s: &str) -> Result<Self, Self::Err> {
72        Self::new(s.to_string())
73    }
74}
75
76impl TryFrom<String> for Identifier {
77    type Error = ValidationError;
78
79    fn try_from(s: String) -> Result<Self, Self::Error> {
80        Self::new(s)
81    }
82}
83
84impl Serialize for Identifier {
85    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86    where
87        S: Serializer,
88    {
89        (*self.0).serialize(serializer)
90    }
91}
92
93impl<'de> Deserialize<'de> for Identifier {
94    fn deserialize<D>(deserializer: D) -> Result<Identifier, D::Error>
95    where
96        D: Deserializer<'de>,
97    {
98        let v: String = Deserialize::deserialize(deserializer)?;
99        let id = unsafe { Identifier::new_unchecked(v) };
100        Ok(id)
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::Identifier;
107    use std::str::FromStr;
108
109    #[test]
110    fn should_create() {
111        assert_eq!(Identifier::new("foo").unwrap().as_str(), "foo");
112        let long_t = (0..256).map(|_| "X").collect::<String>();
113        assert!(Identifier::new(long_t).is_err());
114        assert!(Identifier::new("$").is_err());
115    }
116
117    #[test]
118    fn should_create_unchecked() {
119        unsafe {
120            assert_eq!(Identifier::new_unchecked("foo").as_str(), "foo");
121            assert_eq!(Identifier::new_unchecked("$").as_str(), "$");
122        }
123    }
124
125    #[test]
126    fn should_try_from_str() {
127        assert_eq!(Identifier::try_from("foo".to_string()).unwrap().as_str(), "foo");
128        let long_t = (0..256).map(|_| "X").collect::<String>();
129        assert!(Identifier::try_from(long_t).is_err());
130        assert!(Identifier::try_from("$".to_string()).is_err());
131    }
132
133    #[test]
134    fn should_convert_between_identifier_and_string() {
135        let id = Identifier::new("foo").unwrap();
136        assert_eq!(Identifier::from_str("foo").unwrap(), id);
137        assert_eq!(id.as_str(), "foo");
138        assert_eq!(id.to_string(), "foo".to_string());
139    }
140}