key_path/
lib.rs

1use core::fmt::{Display, Formatter};
2use std::ops::{Add, Index, Range};
3
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub enum Item {
6    Key(String),
7    Index(usize),
8}
9
10impl Display for Item {
11    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
12        match self {
13            Item::Key(s) => f.write_str(s.as_ref()),
14            Item::Index(n) => f.write_str(&n.to_string()),
15        }
16    }
17}
18
19impl Item {
20
21    pub fn is_key(&self) -> bool {
22        use Item::*;
23        match self {
24            Key(_) => true,
25            Index(_) => false,
26        }
27    }
28
29    pub fn is_index(&self) -> bool {
30        use Item::*;
31        match self {
32            Key(_) => false,
33            Index(_) => true,
34        }
35    }
36
37    pub fn as_key(&self) -> Option<&str> {
38        use Item::*;
39        match self {
40            Key(v) => Some(v.as_ref()),
41            Index(_) => None,
42        }
43    }
44
45    pub fn as_index(&self) -> Option<usize> {
46        use Item::*;
47        match self {
48            Key(_) => None,
49            Index(v) => Some(*v),
50        }
51    }
52}
53
54impl From<usize> for Item {
55    fn from(index: usize) -> Self {
56        use Item::*;
57        Index(index)
58    }
59}
60
61impl From<&str> for Item {
62    fn from(key: &str) -> Self {
63        use Item::*;
64        Key(String::from(key))
65    }
66}
67
68impl From<String> for Item {
69    fn from(key: String) -> Self {
70        use Item::*;
71        Key(String::from(key))
72    }
73}
74
75impl From<&String> for Item {
76    fn from(key: &String) -> Self {
77        use Item::*;
78        Key(String::from(key.as_str()))
79    }
80}
81
82#[derive(Clone, Debug, PartialEq, Eq)]
83pub struct KeyPath {
84    items: Vec<Item>
85}
86
87impl KeyPath {
88
89    pub fn new(items: Vec<Item>) -> Self {
90        Self { items }
91    }
92
93    pub fn len(&self) -> usize {
94        self.items.len()
95    }
96
97    pub fn get(&self, index: usize) -> Option<&Item> {
98        self.items.get(index)
99    }
100
101    pub fn last(&self) -> Option<&Item> {
102        self.items.last()
103    }
104
105    pub fn is_empty(&self) -> bool {
106        self.items.is_empty()
107    }
108
109    pub fn iter(&self) -> KeyPathIter {
110        KeyPathIter { key_path: self, index: 0 }
111    }
112}
113
114impl Default for KeyPath {
115    fn default() -> Self {
116        Self { items: vec![] }
117    }
118}
119
120impl AsRef<KeyPath> for KeyPath {
121    fn as_ref(&self) -> &KeyPath {
122        &self
123    }
124}
125
126impl Display for KeyPath {
127    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
128        let s = self.items.iter().map(|i| i.to_string()).collect::<Vec<String>>().join(".");
129        f.write_str(&s)
130    }
131}
132
133impl From<KeyPath> for String {
134    fn from(value: KeyPath) -> Self {
135        value.to_string()
136    }
137}
138
139impl From<&KeyPath> for String {
140    fn from(value: &KeyPath) -> Self {
141        value.to_string()
142    }
143}
144
145impl<'a, T> Add<T> for &KeyPath where T: Into<Item> {
146    type Output = KeyPath;
147
148    fn add(self, rhs: T) -> Self::Output {
149        let mut items = self.items.clone();
150        items.push(rhs.into());
151        KeyPath { items }
152    }
153}
154
155impl<'a, T> Add<T> for KeyPath where T: Into<Item> {
156    type Output = Self;
157
158    fn add(self, rhs: T) -> Self::Output {
159        (&self).add(rhs)
160    }
161}
162
163impl Index<usize> for KeyPath {
164    type Output = Item;
165
166    fn index(&self, index: usize) -> &Self::Output {
167        &self.items[index]
168    }
169}
170
171impl Index<Range<usize>> for KeyPath {
172    type Output = [Item];
173
174    fn index(&self, index: Range<usize>) -> &Self::Output {
175        &self.items[index]
176    }
177}
178
179impl From<&[Item]> for KeyPath {
180    fn from(items: &[Item]) -> Self {
181        Self { items: items.to_vec() }
182    }
183}
184
185pub struct KeyPathIter<'a> {
186    key_path: &'a KeyPath,
187    index: usize,
188}
189
190impl<'a> Iterator for KeyPathIter<'a> {
191    type Item = &'a Item;
192
193    fn next(&mut self) -> Option<Self::Item> {
194        let result = self.key_path.get(self.index);
195        self.index += 1;
196        result
197    }
198}
199
200impl<'a> IntoIterator for &'a KeyPath {
201    type Item = &'a Item;
202    type IntoIter = KeyPathIter<'a>;
203
204    fn into_iter(self) -> Self::IntoIter {
205        KeyPathIter { key_path: self, index: 0 }
206    }
207}
208
209impl<'a> IntoIterator for KeyPath {
210    type Item = Item;
211    type IntoIter = <Vec<Item> as IntoIterator>::IntoIter;
212
213    fn into_iter(self) -> Self::IntoIter {
214        self.items.into_iter()
215    }
216}
217
218#[macro_export]
219macro_rules! path {
220    (@single $($x:tt)*) => (());
221    (@count $($rest:expr),*) => (<[()]>::len(&[$(path!(@single $rest)),*]));
222    (@item $other: expr) => ($crate::Item::from($other));
223    ($($key:expr,)+) => { path!($($key),+) };
224    ($($key:expr),*) => {
225        {
226            let _cap = path!(@count $($key),*);
227            let mut _items = ::std::vec::Vec::with_capacity(_cap);
228            $(
229                let _ = _items.push(path!(@item $key));
230            )*
231            $crate::KeyPath::new(_items)
232        }
233    };
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239
240    #[test]
241    fn macro_works_for_empty() {
242        let result = path![];
243        assert_eq!(result, KeyPath::default());
244    }
245
246    #[test]
247    fn macro_works_for_2_strings() {
248        let result = path!["a", "b"];
249        assert_eq!(result, KeyPath { items: vec![Item::Key("a".into()), Item::Key("b".into())]});
250    }
251
252    #[test]
253    fn macro_works_for_2_numbers() {
254        let result = path![2, 5];
255        assert_eq!(result, KeyPath { items: vec![Item::Index(2), Item::Index(5)]});
256    }
257
258    #[test]
259    fn macro_works_for_2_mixed_items() {
260        let string = "where".to_owned();
261        let result = path![string, 5];
262        assert_eq!(result, KeyPath { items: vec![Item::Key("where".to_owned().into()), Item::Index(5)]});
263    }
264
265    #[test]
266    fn macro_works_for_2_items_with_trailing_comma() {
267        let string = "where".to_owned();
268        let result = path![string, 5,];
269        assert_eq!(result, KeyPath { items: vec![Item::Key("where".to_owned().into()), Item::Index(5)]});
270    }
271
272    #[test]
273    fn macro_works_for_3_items() {
274        let string = "where".to_owned();
275        let result = path![string, 5, 7];
276        assert_eq!(result, KeyPath { items: vec![Item::Key("where".to_owned().into()), Item::Index(5), Item::Index(7)]});
277    }
278
279    #[test]
280    fn macro_works_for_3_items_with_trailing_comma() {
281        let string = "where".to_owned();
282        let result = path![string, 5, 7, ];
283        assert_eq!(result, KeyPath { items: vec![Item::Key("where".to_owned().into()), Item::Index(5), Item::Index(7)]});
284    }
285
286    #[test]
287    fn add_works_for_number() {
288        let path = KeyPath::default();
289        let result = path + 45;
290        assert_eq!(result, KeyPath { items: vec![Item::Index(45)] })
291    }
292
293    #[test]
294    fn add_works_for_str() {
295        let path = KeyPath::default();
296        let result = path + "";
297        assert_eq!(result, KeyPath { items: vec![Item::Key("".into())] })
298    }
299
300    #[test]
301    fn add_works_for_string() {
302        let path = KeyPath::default();
303        let result = path + "a".to_owned();
304        assert_eq!(result, KeyPath { items: vec![Item::Key("a".to_owned().into())] })
305    }
306
307    #[test]
308    fn add_works_for_string_ref() {
309        let path = KeyPath::default();
310        let string = "abc".to_owned();
311        let string_ref = &string;
312        let result = path + string_ref;
313        assert_eq!(result, KeyPath { items: vec![Item::Key("abc".into())] })
314    }
315
316    #[test]
317    fn key_path_can_be_debug_printed() {
318        let path = path!["where", "items", 5, "name"];
319        let result = format!("{}", path);
320        assert_eq!(&result, "where.items.5.name");
321    }
322
323    #[test]
324    fn index_works() {
325        let path = path!["orderBy", "name"];
326        let result = &path[0];
327        assert_eq!(result, &("orderBy".into()))
328    }
329
330    #[test]
331    fn index_with_range_works() {
332        let path = path!["orderBy", "name", 3, "good"];
333        let result = &path[0..2];
334        assert_eq!(result, &[Item::Key("orderBy".to_string()), Item::Key("name".to_string())])
335    }
336
337    #[test]
338    fn get_works() {
339        let path = path!["orderBy", "name"];
340        let result = path.get(0).unwrap();
341        assert_eq!(result, &("orderBy".into()))
342    }
343
344    #[test]
345    fn last_works() {
346        let path = path!["orderBy", "name"];
347        let result = path.last().unwrap();
348        assert_eq!(result, &("name".into()))
349    }
350
351    #[test]
352    fn as_ref_works() {
353        let path = path!["a", "b"];
354        let path2 = path.as_ref();
355        let path3 = path2.as_ref();
356        assert_eq!(&path, path2);
357        assert_eq!(path2, path3);
358
359    }
360
361    #[test]
362    fn to_string_works() {
363        let path = path!["a", 2, "3"];
364        assert_eq!(&path.to_string(), "a.2.3");
365    }
366
367    #[test]
368    fn ref_to_string_works() {
369        let path = path!["a", 2, "3"];
370        let path_ref = &path;
371        assert_eq!(&path_ref.to_string(), "a.2.3");
372    }
373
374    #[test]
375    fn iter_works() {
376        let path = path!["a", 2, "3"];
377        let path = &path;
378        let mut result = "".to_owned();
379        for item in path {
380            result += format!("{}", item).as_str();
381        }
382        assert_eq!(&result, "a23");
383    }
384
385    #[test]
386    fn into_iter_works() {
387        let path = path!["a", 2, "3"];
388        let mut result = "".to_owned();
389        for item in path {
390            result += format!("{}", item).as_str();
391        }
392        assert_eq!(&result, "a23");
393    }
394}