indradb/models/
identifiers.rs1use 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#[derive(Eq, PartialEq, Copy, Clone, Debug, Hash, Ord, PartialOrd)]
14pub struct Identifier(pub(crate) Intern<String>);
15
16impl Identifier {
17 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 pub unsafe fn new_unchecked<S: Into<String>>(s: S) -> Self {
46 Self(Intern::new(s.into()))
47 }
48
49 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}