streamson_lib/
path.rs

1//! Structs to handle abstraction over a path in JSON
2
3use crate::error;
4use std::{cmp::Ordering, convert::TryFrom, fmt, hash::Hash};
5
6/// An element of the path
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub enum Element {
9    Key(String),
10    Index(usize),
11}
12
13impl Element {
14    pub fn is_key(&self) -> bool {
15        matches!(self, Element::Key(_))
16    }
17
18    pub fn is_index(&self) -> bool {
19        matches!(self, Element::Index(_))
20    }
21}
22
23impl PartialOrd for Element {
24    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
25        Some(self.cmp(other))
26    }
27}
28
29impl Ord for Element {
30    fn cmp(&self, other: &Self) -> Ordering {
31        match (self, other) {
32            (Element::Key(this_key), Element::Key(other_key)) => this_key.cmp(other_key),
33            (Element::Index(this_idx), Element::Index(other_idx)) => this_idx.cmp(other_idx),
34            (Element::Key(_), Element::Index(_)) => Ordering::Less,
35            (Element::Index(_), Element::Key(_)) => Ordering::Greater,
36        }
37    }
38}
39
40impl fmt::Display for Element {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            Self::Key(string) => write!(f, "{{\"{}\"}}", string),
44            Self::Index(idx) => write!(f, "[{}]", idx),
45        }
46    }
47}
48
49/// Represents the path in a json
50/// e.g. {"users"}[0]
51#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
52pub struct Path {
53    path: Vec<Element>,
54}
55
56impl Path {
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    /// Removes last path element
62    pub fn pop(&mut self) -> Option<Element> {
63        self.path.pop()
64    }
65
66    /// Appends path element
67    pub fn push(&mut self, element: Element) {
68        self.path.push(element);
69    }
70
71    /// Returns the path depth
72    pub fn depth(&self) -> usize {
73        self.path.len()
74    }
75
76    /// Returns the actual path
77    pub fn get_path(&self) -> &[Element] {
78        &self.path
79    }
80}
81
82/// Path parsing state
83#[derive(Debug, PartialEq)]
84enum PathState {
85    ElementStart,
86    Array,
87    ObjectStart,
88    Object(bool),
89    ObjectEnd,
90}
91
92impl TryFrom<&str> for Path {
93    type Error = error::Path;
94
95    fn try_from(path_str: &str) -> Result<Self, Self::Error> {
96        let mut state = PathState::ElementStart;
97        let mut path = Self::new();
98        let mut buffer = vec![];
99        for chr in path_str.chars() {
100            state = match state {
101                PathState::ElementStart => match chr {
102                    '[' => PathState::Array,
103                    '{' => PathState::ObjectStart,
104                    _ => return Err(error::Path::new(path_str)),
105                },
106                PathState::Array => match chr {
107                    '0'..='9' => {
108                        buffer.push(chr);
109                        PathState::Array
110                    }
111                    ']' => {
112                        let idx: usize = buffer.drain(..).collect::<String>().parse().unwrap();
113                        path.push(Element::Index(idx));
114                        PathState::ElementStart
115                    }
116                    _ => return Err(error::Path::new(path_str)),
117                },
118                PathState::ObjectStart => {
119                    if chr == '"' {
120                        PathState::Object(false)
121                    } else {
122                        return Err(error::Path::new(path_str));
123                    }
124                }
125                PathState::Object(escaped) => {
126                    if escaped {
127                        buffer.push(chr);
128                        PathState::Object(false)
129                    } else {
130                        match chr {
131                            '\\' => {
132                                buffer.push(chr);
133                                PathState::Object(true)
134                            }
135                            '"' => PathState::ObjectEnd,
136                            _ => {
137                                buffer.push(chr);
138                                PathState::Object(false)
139                            }
140                        }
141                    }
142                }
143                PathState::ObjectEnd => {
144                    if chr == '}' {
145                        let key: String = buffer.drain(..).collect();
146                        path.push(Element::Key(key));
147                        PathState::ElementStart
148                    } else {
149                        return Err(error::Path::new(path_str));
150                    }
151                }
152            };
153        }
154        if state == PathState::ElementStart {
155            Ok(path)
156        } else {
157            Err(error::Path::new(path_str))
158        }
159    }
160}
161
162impl fmt::Display for Path {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        for element in &self.path {
165            write!(f, "{}", element)?;
166        }
167        Ok(())
168    }
169}
170
171impl Ord for Path {
172    fn cmp(&self, other: &Self) -> Ordering {
173        for (a, b) in self.path.iter().zip(other.path.iter()) {
174            let res = a.cmp(b);
175            if res != Ordering::Equal {
176                return res;
177            }
178        }
179        match (self.path.len(), other.path.len()) {
180            (a_len, b_len) if a_len < b_len => Ordering::Less,
181            (a_len, b_len) if a_len == b_len => Ordering::Equal,
182            (a_len, b_len) if a_len > b_len => Ordering::Greater,
183            (_, _) => unreachable!(),
184        }
185    }
186}
187
188impl PartialOrd for Path {
189    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
190        Some(self.cmp(other))
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::{Element, Path};
197    use std::convert::TryFrom;
198
199    #[test]
200    fn test_path_from_string_empty() {
201        assert!(Path::try_from("").is_ok());
202    }
203
204    #[test]
205    fn test_path_from_string_array() {
206        let mut path = Path::new();
207        path.push(Element::Index(0));
208        assert_eq!(Path::try_from("[0]").unwrap(), path);
209    }
210
211    #[test]
212    fn test_path_from_string_object() {
213        let mut path = Path::new();
214        path.push(Element::Key(r#"my-ke\\y\" "#.into()));
215        assert_eq!(Path::try_from(r#"{"my-ke\\y\" "}"#).unwrap(), path);
216    }
217}