Skip to main content

openpathresolver/types/
field_key.rs

1struct FieldKeyVisitor;
2
3impl<'de> serde::de::Visitor<'de> for FieldKeyVisitor {
4    type Value = FieldKey;
5
6    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
7        formatter.write_str("a valid field key")
8    }
9
10    fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
11        FieldKey::new(value).map_err(|err| E::custom(format!("{}", err)))
12    }
13}
14
15/// A field key is a valid key to a field.
16///
17/// This can be used for path parts keys, the parent key, etc.
18///
19/// # Validation
20///
21/// - The key must not be empty
22/// - The first character of the key must be any ASCII alphabetic character or `_`.
23/// - The remainder characters must be any ASCII alphanumeric character or `_`.
24/// - Sections can be split with `.`. The above rules then apply to each section.
25#[derive(Debug, Clone, PartialEq, Eq, Hash)]
26pub struct FieldKey {
27    key: String,
28}
29
30impl serde::Serialize for FieldKey {
31    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
32        serializer.serialize_str(&self.key)
33    }
34}
35
36impl<'de> serde::Deserialize<'de> for FieldKey {
37    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
38    where
39        D: serde::Deserializer<'de>,
40    {
41        deserializer.deserialize_str(FieldKeyVisitor)
42    }
43}
44
45impl std::fmt::Display for FieldKey {
46    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47        write!(f, "{}", self.key)
48    }
49}
50
51impl FieldKey {
52    /// Create a new field key.
53    pub fn new(key: &str) -> Result<Self, crate::Error> {
54        let key = key.to_lowercase();
55        let mut parsed_key = String::new();
56
57        if !Self::validate(&key) {
58            return Err(crate::Error::new("Invalid field key"));
59        }
60
61        Self::parse(&key, &mut parsed_key)?;
62        Ok(Self { key: parsed_key })
63    }
64
65    /// Access the internal key string.
66    pub fn as_str(&self) -> &str {
67        &self.key
68    }
69
70    fn parse(text: &str, writer: &mut impl std::fmt::Write) -> Result<(), crate::Error> {
71        let split_index = match text.find('.') {
72            Some(index) => index,
73            None => {
74                writer.write_str(text)?;
75
76                return Ok(());
77            }
78        };
79        let (before, after) = text.split_at(split_index);
80        writer.write_str(before)?;
81
82        let after = &after[1..];
83
84        if !after.is_empty() {
85            writer.write_char('.')?;
86            Self::parse(after, writer)?;
87        }
88
89        Ok(())
90    }
91
92    pub(crate) fn validate(text: &str) -> bool {
93        if text.is_empty() {
94            return false;
95        }
96
97        let split_index = match text.find('.') {
98            Some(index) => index,
99            None => {
100                if !Self::validate_part(text) {
101                    return false;
102                }
103
104                return true;
105            }
106        };
107        let (before, after) = text.split_at(split_index);
108
109        if !Self::validate_part(before) {
110            return false;
111        }
112
113        let after = &after[1..];
114
115        if !Self::validate(after) {
116            return false;
117        }
118
119        true
120    }
121
122    fn validate_part(text: &str) -> bool {
123        if text.is_empty() {
124            return false;
125        }
126
127        let first_char = text.chars().next().unwrap();
128
129        if !(first_char.is_ascii_alphabetic() || first_char == '_') {
130            return false;
131        }
132
133        for character in text.chars().skip(1) {
134            if !(character.is_ascii_alphanumeric() || character == '_') {
135                return false;
136            }
137        }
138
139        true
140    }
141}
142
143impl TryFrom<&str> for FieldKey {
144    type Error = crate::Error;
145
146    fn try_from(value: &str) -> Result<Self, Self::Error> {
147        Self::new(value)
148    }
149}
150
151impl TryFrom<&String> for FieldKey {
152    type Error = crate::Error;
153
154    fn try_from(value: &String) -> Result<Self, Self::Error> {
155        Self::new(value)
156    }
157}
158
159impl TryFrom<String> for FieldKey {
160    type Error = crate::Error;
161
162    fn try_from(value: String) -> Result<Self, Self::Error> {
163        Self::new(&value)
164    }
165}
166
167impl TryFrom<&FieldKey> for FieldKey {
168    type Error = crate::Error;
169
170    fn try_from(value: &FieldKey) -> Result<Self, Self::Error> {
171        Ok(value.to_owned())
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[rstest::rstest]
180    #[case("test", "test")]
181    #[case("Test", "test")]
182    #[case("_test", "_test")]
183    #[case("test_", "test_")]
184    #[case("_1test", "_1test")]
185    #[case("abc.def", "abc.def")]
186    #[case("abc.def.ghi", "abc.def.ghi")]
187    #[case("abc123.def456", "abc123.def456")]
188    #[case("_abc._def", "_abc._def")]
189    fn test_field_key_new_success(#[case] input: &str, #[case] expected: &str) {
190        // New
191        let result = FieldKey::new(input).unwrap();
192        assert_eq!(&result.key, expected);
193
194        // From<&str>
195        let result = FieldKey::try_from(input).unwrap();
196        assert_eq!(&result.key, expected);
197
198        // From<String>
199        let result = FieldKey::try_from(input.to_string()).unwrap();
200        assert_eq!(&result.key, expected);
201
202        // From<&String>
203        let result = FieldKey::try_from(&input.to_string()).unwrap();
204        assert_eq!(&result.key, expected);
205    }
206
207    #[rstest::rstest]
208    #[case("", "Invalid field key")]
209    #[case(" abc ", "Invalid field key")]
210    #[case("1", "Invalid field key")]
211    #[case("abc.", "Invalid field key")]
212    #[case("abc.123.", "Invalid field key")]
213    #[case("abc.def.", "Invalid field key")]
214    #[case("abc.def.123", "Invalid field key")]
215    #[case("abc..def", "Invalid field key")]
216    #[case(".abc", "Invalid field key")]
217    #[case("1abc", "Invalid field key")]
218    #[case("!", "Invalid field key")]
219    #[case("a!", "Invalid field key")]
220    #[case("abc.!", "Invalid field key")]
221    #[case("abc.d!", "Invalid field key")]
222    #[case(".", "Invalid field key")]
223    #[case("..", "Invalid field key")]
224    fn test_tokens_parse_failure(#[case] input: &str, #[case] expected: &str) {
225        // New
226        let result = FieldKey::new(input).unwrap_err();
227        assert_eq!(result.to_string(), expected);
228
229        // From<&str>
230        let result = FieldKey::try_from(input).unwrap_err();
231
232        assert_eq!(result.to_string(), expected);
233
234        // From<String>
235        let result = FieldKey::try_from(input.to_string()).unwrap_err();
236
237        assert_eq!(result.to_string(), expected);
238
239        // From<&String>
240        let result = FieldKey::try_from(&input.to_string()).unwrap_err();
241
242        assert_eq!(result.to_string(), expected);
243    }
244
245    #[rstest::rstest]
246    #[case("test", "test")]
247    #[case("Test", "test")]
248    #[case("_test", "_test")]
249    #[case("_1test", "_1test")]
250    #[case("abc.def", "abc.def")]
251    #[case("abc.def.ghi", "abc.def.ghi")]
252    #[case("abc123.def456", "abc123.def456")]
253    #[case("_abc._def", "_abc._def")]
254    fn test_field_key_display_success(#[case] input: &str, #[case] expected: &str) {
255        // New
256        let result = FieldKey::new(input).unwrap();
257        assert_eq!(format!("{}", result), expected);
258    }
259
260    #[rstest::rstest]
261    #[case("test", "test")]
262    #[case("test", "Test")]
263    #[case("abc.def", "abc.def")]
264    fn test_field_key_eq(#[case] input: &str, #[case] other: &str) {
265        let input = FieldKey::new(input).unwrap();
266        let other = FieldKey::new(other).unwrap();
267
268        assert_eq!(input, other);
269    }
270
271    #[rstest::rstest]
272    #[case("test", "test1")]
273    #[case("test", "Test1")]
274    #[case("abc.def", "abc")]
275    #[case("abc", "abc.def")]
276    #[case("abc.def", "abc.def1")]
277    fn test_field_key_ne(#[case] input: &str, #[case] other: &str) {
278        let input = FieldKey::new(input).unwrap();
279        let other = FieldKey::new(other).unwrap();
280
281        assert_ne!(input, other);
282    }
283}