chisel_json/pointers/
pointer.rs

1//! A representation of a JSON Pointer with associated operations, as per RFC 6901
2//!
3//!
4use std::{borrow::Cow, collections::VecDeque, fmt::Display, ops::Add};
5
6/// Each pointer is a series of segments delineated by a separator char
7const PATH_SEPARATOR: char = '/';
8/// As per the RFC, we need to encode any tilde characters as ~0
9const ENCODED_TILDE: &str = "~0";
10/// As per the RFC, we need to encode any slash characters as ~1
11const ENCODED_SLASH: &str = "~1";
12
13/// Each pointer is made of one of three different component types
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub enum JsonPointerComponent<'a> {
16    /// Root element of a pointer
17    Root,
18    /// A named element within a pointer
19    Name(Cow<'a, str>),
20    /// An indexed element within a pointer
21    Index(usize),
22}
23
24impl<'a> Display for JsonPointerComponent<'a> {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            Self::Root => write!(f, ""),
28            Self::Name(s) => write!(
29                f,
30                "{}",
31                &s.replace("~", ENCODED_TILDE).replace("/", ENCODED_SLASH)
32            ),
33            Self::Index(i) => write!(f, "{}", i),
34        }
35    }
36}
37
38/// A structure representing a complete pointer, comprising multiple [JsonPointerComponent]s
39#[derive(Debug, Default, Hash, Eq)]
40pub struct JsonPointer<'a> {
41    /// The components that go together to make up the pointer
42    components: VecDeque<JsonPointerComponent<'a>>,
43}
44
45impl<'a> PartialEq for JsonPointer<'a> {
46    fn eq(&self, other: &Self) -> bool {
47        self.matches(other)
48    }
49}
50
51impl<'a> JsonPointer<'a> {
52    /// Returns a root [JsonPointer] instance
53    pub fn root() -> Self {
54        let mut ptr = JsonPointer::default();
55        ptr.components.push_back(JsonPointerComponent::Root);
56        ptr
57    }
58
59    /// Checks to see if a pointer is just a top level root element
60    pub fn is_root(&self) -> bool {
61        return !self.components.is_empty()
62            && self.components.len() == 1
63            && self.components[0] == JsonPointerComponent::Root;
64    }
65
66    /// Returns the number of [JsonPointerComponent]s within the pointer
67    pub fn len(&self) -> usize {
68        self.components.len()
69    }
70
71    /// Checks whether the pointer is the empty pointer
72    pub fn is_empty(&self) -> bool {
73        self.components.is_empty()
74    }
75
76    /// Push a whole bunch of names onto the end of the path in order
77    pub fn push_names(&mut self, names: &[&'a str]) {
78        names.iter().for_each(|n| self.push_name(n.to_string()))
79    }
80
81    /// Push a whole bunch of indexes onto the end of the path in order
82    pub fn push_indexes(&mut self, indexes: &[usize]) {
83        indexes.iter().for_each(|i| self.push_index(*i))
84    }
85
86    /// Push a new [JsonPointerComponent::Name] onto the end of the pointer
87    pub fn push_name(&mut self, name: String) {
88        if self.is_empty() {
89            self.components.push_back(JsonPointerComponent::Root)
90        }
91        self.components
92            .push_back(JsonPointerComponent::Name(Cow::Owned(name)))
93    }
94
95    /// Push a new [JsonPointerComponent::Index] onto the end of the pointer
96    pub fn push_index(&mut self, index: usize) {
97        if self.is_empty() {
98            self.components.push_back(JsonPointerComponent::Root)
99        }
100        self.components
101            .push_back(JsonPointerComponent::Index(index))
102    }
103
104    /// Pop the last component off the back of the pointer
105    pub fn pop(&mut self) -> Option<JsonPointerComponent<'a>> {
106        self.components.pop_back()
107    }
108
109    /// Checks whether a path matches another path.
110    pub fn matches(&self, rhs: &'a JsonPointer) -> bool {
111        self.as_str() == rhs.as_str()
112    }
113
114    /// Serialise the pointer into a string representation that's compliant with RFC 6901
115    pub fn as_str(&self) -> Cow<'a, str> {
116        // check to see if we are a top level root
117        if self.is_root() {
118            return Cow::Owned("/".to_string());
119        }
120
121        // check to see if we are truly empty
122        if self.is_empty() {
123            return Cow::Owned("".to_string());
124        }
125
126        // otherwise, we have multiple components
127        Cow::Owned(
128            self.components
129                .iter()
130                .map(|c| c.to_string())
131                .collect::<Vec<String>>()
132                .join("/"),
133        )
134    }
135}
136
137impl<'a> Display for JsonPointer<'a> {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        write!(f, "{}", self.as_str())
140    }
141}
142
143impl<'a> Add<&JsonPointer<'a>> for JsonPointer<'a> {
144    type Output = Self;
145
146    /// Concatenate two [JsonPointer] instances.
147    fn add(mut self, rhs: &JsonPointer<'a>) -> Self {
148        rhs.components
149            .iter()
150            .for_each(|c| self.components.push_back(c.clone()));
151        self
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::JsonPointer;
158
159    #[test]
160    fn an_empty_pointer_should_be_represented_by_an_empty_string() {
161        let s = JsonPointer::default().as_str();
162        assert_eq!(s, "")
163    }
164
165    #[test]
166    fn a_root_pointer_should_be_represented_by_a_single_slash() {
167        let s = JsonPointer::root().as_str();
168        assert_eq!(s, "/")
169    }
170
171    #[test]
172    fn pointers_should_serialise_correctly() {
173        let mut s = JsonPointer::default();
174        s.push_names(&vec!["a", "b"]);
175        assert_eq!("/a/b", s.as_str())
176    }
177
178    #[test]
179    fn pointers_should_serialise_with_escapes_correctly() {
180        let mut s = JsonPointer::default();
181        s.push_names(&vec!["a/b", "c~d"]);
182        s.push_index(3);
183        assert_eq!("/a~1b/c~0d/3", s.as_str())
184    }
185
186    #[test]
187    fn popping_should_shorten_pointers_correctly() {
188        let mut s = JsonPointer::default();
189        s.push_names(&vec!["a", "b", "c"]);
190        assert_eq!("/a/b/c", s.as_str());
191        s.pop();
192        assert_eq!("/a/b", s.as_str())
193    }
194
195    #[test]
196    fn popping_all_components_should_result_in_a_root_pointer() {
197        let mut s = JsonPointer::default();
198        s.push_names(&vec!["a", "b", "c"]);
199        s.pop();
200        s.pop();
201        s.pop();
202        assert_eq!("/", s.as_str())
203    }
204
205    #[test]
206    fn pointers_should_serialise_indices_correctly() {
207        let mut s = JsonPointer::default();
208        s.push_index(0);
209        s.push_index(3);
210        s.push_index(2);
211        assert_eq!("/0/3/2", s.as_str())
212    }
213
214    #[test]
215    fn pointers_should_match() {
216        let mut s = JsonPointer::default();
217        let mut t = JsonPointer::default();
218        s.push_name("b".to_string());
219        s.push_index(9);
220        t.push_name("b".to_string());
221        t.push_index(9);
222        assert!(s.matches(&t))
223    }
224
225    #[test]
226    fn pointers_should_match_using_equality_ops() {
227        let mut s = JsonPointer::default();
228        let mut t = JsonPointer::default();
229        let mut u = JsonPointer::default();
230        s.push_name("b".to_string());
231        s.push_index(9);
232        t.push_name("b".to_string());
233        t.push_index(9);
234        u.push_name("x".to_string());
235        assert_eq!(s, t);
236        assert_ne!(t, u);
237        assert_ne!(s, u)
238    }
239}