substrate_data/model/
reference.rs1use std::fmt;
5
6use crate::Atom;
7use crate::typed::TypedError;
8
9#[derive(Clone, Copy, PartialEq, Eq, Hash)]
13pub struct KindName(Atom);
14
15impl KindName {
16 pub fn new(name: &str) -> Result<KindName, TypedError> {
23 if Self::is_valid(name) {
24 Ok(KindName(Atom::new(name)))
25 } else {
26 Err(TypedError::BadKind {
27 kind: name.to_owned(),
28 })
29 }
30 }
31
32 #[must_use]
34 pub fn declared(name: &'static str) -> KindName {
35 debug_assert!(Self::is_valid(name), "`{name}` isn't a kind");
36 KindName(Atom::new(name))
37 }
38
39 #[must_use]
42 pub const fn is_valid(name: &str) -> bool {
43 let b = name.as_bytes();
44 if b.is_empty() || !b[0].is_ascii_lowercase() {
45 return false;
46 }
47 let mut i = 1;
48 while i < b.len() {
49 let c = b[i];
50 if !(c.is_ascii_lowercase() || c.is_ascii_digit() || c == b'_') {
51 return false;
52 }
53 i += 1;
54 }
55 true
56 }
57
58 #[must_use]
60 pub fn as_str(self) -> &'static str {
61 self.0.as_str()
62 }
63
64 #[must_use]
66 pub fn atom(self) -> Atom {
67 self.0
68 }
69}
70
71impl fmt::Debug for KindName {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 write!(f, "KindName({:?})", self.as_str())
74 }
75}
76
77impl fmt::Display for KindName {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 f.write_str(self.as_str())
80 }
81}
82
83impl PartialOrd for KindName {
84 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
85 Some(self.cmp(other))
86 }
87}
88
89impl Ord for KindName {
90 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
92 self.as_str().cmp(other.as_str())
93 }
94}
95
96impl PartialEq<str> for KindName {
97 fn eq(&self, other: &str) -> bool {
98 self.as_str() == other
99 }
100}
101
102impl PartialEq<&str> for KindName {
103 fn eq(&self, other: &&str) -> bool {
104 self.as_str() == *other
105 }
106}
107
108#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
112pub struct RefKey(Box<str>);
113
114impl RefKey {
115 pub fn new(id: &str) -> Result<RefKey, TypedError> {
121 if Self::is_valid(id) {
122 Ok(RefKey(id.into()))
123 } else {
124 Err(TypedError::BadId {
125 kind: None,
126 id: id.to_owned(),
127 })
128 }
129 }
130
131 #[must_use]
133 pub fn is_valid(id: &str) -> bool {
134 if id.is_empty() {
135 return false;
136 }
137 let mut ascii = true;
140 for &c in id.as_bytes() {
141 if !c.is_ascii() {
142 ascii = false;
143 break;
144 }
145 if c <= b' ' || c == 0x7f {
146 return false;
147 }
148 }
149 ascii || !id.chars().any(|c| c.is_whitespace() || c.is_control())
150 }
151
152 pub(crate) fn spelled(id: &str) -> RefKey {
154 RefKey(id.into())
155 }
156
157 #[must_use]
159 pub fn as_str(&self) -> &str {
160 &self.0
161 }
162}
163
164impl fmt::Debug for RefKey {
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 write!(f, "RefKey({:?})", &*self.0)
167 }
168}
169
170impl fmt::Display for RefKey {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 f.write_str(&self.0)
173 }
174}
175
176impl PartialEq<str> for RefKey {
177 fn eq(&self, other: &str) -> bool {
178 &*self.0 == other
179 }
180}
181
182impl PartialEq<&str> for RefKey {
183 fn eq(&self, other: &&str) -> bool {
184 &*self.0 == *other
185 }
186}
187
188impl std::ops::Deref for KindName {
189 type Target = str;
190 fn deref(&self) -> &str {
191 self.as_str()
192 }
193}
194
195impl AsRef<str> for KindName {
196 fn as_ref(&self) -> &str {
197 self.as_str()
198 }
199}
200
201impl PartialEq<String> for KindName {
202 fn eq(&self, other: &String) -> bool {
203 self.as_str() == other
204 }
205}
206
207impl std::ops::Deref for RefKey {
208 type Target = str;
209 fn deref(&self) -> &str {
210 &self.0
211 }
212}
213
214impl AsRef<str> for RefKey {
215 fn as_ref(&self) -> &str {
216 &self.0
217 }
218}
219
220impl std::borrow::Borrow<str> for RefKey {
222 fn borrow(&self) -> &str {
223 &self.0
224 }
225}
226
227impl PartialEq<String> for RefKey {
228 fn eq(&self, other: &String) -> bool {
229 &*self.0 == other
230 }
231}
232
233impl PartialEq<RefKey> for String {
234 fn eq(&self, other: &RefKey) -> bool {
235 self.as_str() == &*other.0
236 }
237}
238
239impl PartialEq<RefKey> for str {
240 fn eq(&self, other: &RefKey) -> bool {
241 self == &*other.0
242 }
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn an_id_is_anything_but_empty_whitespace_or_controls() {
251 for ok in [
252 "a",
253 "images/logo.png",
254 "~!@#$%^&*()_+-=[]{};:'\",.<>?/|\\`",
255 "héllo",
256 "日本",
257 ] {
258 assert!(RefKey::is_valid(ok), "{ok:?}");
259 }
260 for bad in [
261 "",
262 " ",
263 "a b",
264 "a\tb",
265 "a\nb",
266 "a\rb",
267 "a\x0bb",
268 "a\x0cb",
269 "a\0b",
270 "a\x1fb",
271 "a\x7fb",
272 "é b",
273 "é\u{85}",
274 "é\u{a0}",
275 "é\u{2003}",
276 "日本\u{9f}",
277 "a\u{200a}",
278 ] {
279 assert!(!RefKey::is_valid(bad), "{bad:?}");
280 }
281 }
282
283 #[test]
284 fn the_ascii_path_agrees_with_the_character_rule() {
285 for byte in 0..=0x7f_u8 {
286 let id = format!("a{}b", char::from(byte));
287 let by_chars = !id.chars().any(|c| c.is_whitespace() || c.is_control());
288 assert_eq!(RefKey::is_valid(&id), by_chars, "{id:?}");
289 }
290 }
291}