1use std::hash::Hash;
2use std::sync::Arc;
3
4use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
5
6use crate::error::ErrorKind;
7
8#[derive(Debug, Clone, Eq, Hash, PartialEq)]
18pub struct Identifier(Arc<str>);
19
20impl Identifier {
21 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 #[cfg(test)]
35 pub(crate) fn new_raw(string: &str) -> Self {
36 assert!(is_valid_identifier(string));
37 Self(string.into())
38 }
39
40 #[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 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
69impl<'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}