chisel_json_pointer/
lib.rs

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