json_toolkit/
pointer.rs

1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::str::FromStr;
4
5use derive_more::Display;
6
7use crate::Error;
8
9fn decode_token(s: &str) -> String {
10    s.replace("~1", "/").replace("~0", "~")
11}
12
13/// JSON pointer representation based on [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901).
14///
15/// This type offers strong ordering over the underlying Unicode string:
16/// - JSON pointers are sorted by ascending depth.
17/// - JSON pointers with the same depth are alphanumerically sorted.
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[derive(Debug, Display, Clone, PartialEq, Eq, Hash)]
20#[display(fmt = "{}", .0)]
21pub struct Pointer<'a>(Cow<'a, str>);
22
23impl<'a> Pointer<'a> {
24    /// Creates a `Pointer` from a Unicode string as describe in [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901#section-3).
25    ///
26    /// # Arguments
27    /// * `s`: A Unicode string representing a JSON pointer.
28    ///
29    /// # Examples
30    /// ```
31    /// # use json_toolkit::Pointer;
32    ///
33    /// // Construct a `Pointer` from a string literal.
34    /// let pointer = Pointer::new("/a/b/c").unwrap();
35    ///
36    /// // Construct a `Pointer` from a owned string.
37    /// let pointer = Pointer::new(String::from("/a/b/c")).unwrap();
38    /// ```
39
40    pub fn new(s: impl Into<Cow<'a, str>>) -> Result<Self, Error> {
41        let pointer = s.into();
42
43        if !pointer.is_empty() && !pointer.starts_with('/') {
44            Err(Error::MissingLeadingBackslash)
45        } else {
46            Ok(Self(pointer))
47        }
48    }
49
50    /// Creates a root JSON pointer.
51    pub const fn root() -> Self {
52        Self(Cow::Borrowed(""))
53    }
54
55    /// Indicates if the JSON pointer points to root value.
56    pub fn is_root(&self) -> bool {
57        self.0.is_empty()
58    }
59
60    /// Returns the Unicode string representation of the JSON pointer.
61    pub fn as_str(&self) -> &str {
62        &*self.0
63    }
64
65    /// Returns the last reference token of the JSON pointer, also called JSON key.
66    ///
67    /// Note that the root pointer does not contains any reference tokens and so no JSON key.
68    ///
69    /// # Example
70    /// ```
71    /// # use json_toolkit::Pointer;
72    ///
73    /// let pointer = Pointer::new("/key").unwrap();
74    /// assert_eq!(pointer.key(), Some("key".to_string()));
75    ///
76    /// let pointer = Pointer::root();
77    /// assert!(pointer.key().is_none());
78    /// ```
79    pub fn key(&self) -> Option<String> {
80        self.0.rsplit_once('/').map(|(_, token)| decode_token(token))
81    }
82
83    /// Returns the parent JSON pointer.
84    ///
85    /// Note that the returned JSON pointer borrows a part of the underlying Unicode string then it can be
86    /// [`clone`](Clone::clone) without any extra allocation.
87    ///
88    /// # Example
89    /// ```
90    /// # use json_toolkit::Pointer;
91    ///
92    /// let pointer = Pointer::new("/nested/key").unwrap();
93    /// let parent_pointer = Pointer::new("/nested").unwrap();
94    ///
95    /// assert_eq!(pointer.parent(), Some(parent_pointer));
96    /// ```
97    pub fn parent(&self) -> Option<Pointer<'_>> {
98        self.0
99            .rsplit_once('/')
100            .map(|(parent, _)| Pointer(Cow::Borrowed(parent)))
101    }
102
103    /// Produces an iterator over `Pointer` and its parent JSON pointers.
104    ///
105    /// As [`Pointer::parent`] method, all the returned JSON pointers borrow parts of the underlying Unicode string
106    /// then any of them can be [`clone`](Clone::clone) without any extra allocation.
107    ///
108    /// The iterator will yield the `Pointer` then its parents like `self`, `self.parent().unwrap()`,
109    /// `self.parent().unwrap().parent().unwrap()` and so on until reaching the root JSON pointer.
110    ///
111    /// # Examples
112    /// ```
113    /// # use json_toolkit::Pointer;
114    ///
115    /// let pointer = Pointer::new("/foo/bar/zoo").unwrap();
116    /// let ancestors = pointer.ancestors().collect::<Vec<_>>();
117    ///
118    /// assert_eq!(
119    ///     ancestors,
120    ///     vec![
121    ///         Pointer::new("/foo/bar/zoo").unwrap(),
122    ///         Pointer::new("/foo/bar").unwrap(),
123    ///         Pointer::new("/foo").unwrap(),
124    ///         Pointer::root()
125    ///     ]
126    /// );
127    ///
128    /// ```
129    pub fn ancestors(&self) -> impl Iterator<Item = Pointer<'_>> {
130        self.0
131            .match_indices('/')
132            .map(|(i, _)| i)
133            .chain([self.0.len()])
134            .rev()
135            .map(|i| Pointer(Cow::Borrowed(&self.0[0..i])))
136    }
137
138    /// Indicates if `Pointer` is an ancestor of the given JSON pointer.
139    ///
140    /// Note that `Pointer` is an ancestor of itself.
141    pub fn is_ancestor_of(&self, other: &Pointer<'_>) -> bool {
142        other.ancestors().any(|pointer| pointer == *self)
143    }
144
145    /// Indicates if `Pointer` is a parent of the given JSON pointer.
146    ///
147    /// Note that the root JSON pointer is the only one with no parent.
148    pub fn is_parent_of(&self, other: &Pointer<'_>) -> bool {
149        other.parent().as_ref() == Some(self)
150    }
151
152    /// Indicates if `Pointer` is a sibling of the given JSON pointer.
153    pub fn is_sibling_of(&self, other: &Pointer<'_>) -> bool {
154        self != other && self.parent() == other.parent()
155    }
156
157    /// Indicates the number of reference tokens in the JSON pointer, in a zero-based indexed way.
158    pub fn depth(&self) -> usize {
159        self.0.split('/').skip(1).count()
160    }
161
162    /// Creates an owned instance of `Pointer`.
163    ///
164    /// Note that this function may call `Clone::clone` if the underlying Unicode string is borrowed.
165    pub fn into_owned(self) -> Pointer<'static> {
166        Pointer(Cow::Owned(self.0.into_owned()))
167    }
168
169    /// Evaluates `Pointer` into tokens as define in [RFC6901](https://datatracker.ietf.org/doc/html/rfc6901#section-4).
170    ///
171    /// # Examples
172    /// ```
173    /// # use json_toolkit::Pointer;
174    ///
175    /// let pointer = Pointer::new("/~1foo/~0bar/zoo").unwrap();
176    /// let tokens = pointer.tokenize().collect::<Vec<_>>();
177    ///
178    /// assert_eq!(
179    ///     tokens,
180    ///     vec![
181    ///         "/foo".to_string(),
182    ///         "~bar".to_string(),
183    ///         "zoo".to_string(),
184    ///     ]
185    /// );
186    /// ```
187    pub fn tokenize(&'a self) -> impl Iterator<Item = String> + 'a {
188        self.0.split('/').skip(1).map(decode_token)
189    }
190}
191
192impl FromStr for Pointer<'_> {
193    type Err = Error;
194
195    fn from_str(s: &str) -> Result<Self, Self::Err> {
196        Self::new(s.to_owned())
197    }
198}
199
200impl<'a> TryFrom<&'a str> for Pointer<'a> {
201    type Error = Error;
202
203    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
204        Self::new(s)
205    }
206}
207
208impl TryFrom<String> for Pointer<'_> {
209    type Error = Error;
210
211    fn try_from(s: String) -> Result<Self, Self::Error> {
212        Self::new(s)
213    }
214}
215
216impl AsRef<str> for Pointer<'_> {
217    fn as_ref(&self) -> &str {
218        self.as_str()
219    }
220}
221
222impl Ord for Pointer<'_> {
223    fn cmp(&self, other: &Self) -> Ordering {
224        match self.depth().cmp(&other.depth()) {
225            Ordering::Equal => self.0.cmp(&other.0),
226            ordering => ordering,
227        }
228    }
229}
230
231impl PartialOrd for Pointer<'_> {
232    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
233        Some(self.cmp(other))
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn it_accepts_valid_json_pointer() -> Result<(), Error> {
243        let tests = [
244            // point to root JSON value
245            "",
246            // point to an empty key in the root JSON value
247            "/",
248            "/path/to/object",
249            "/path/to/an/array/0/dummy",
250        ];
251
252        for s in tests {
253            let result = Pointer::new(s);
254
255            assert!(result.is_ok(), "'{}' is a valid JSON pointer", s);
256        }
257
258        Ok(())
259    }
260
261    #[test]
262    fn it_rejects_json_pointer_without_leading_backslash() {
263        let s = "path/without/leading/backslash";
264        let e = Pointer::new(s);
265
266        assert_eq!(e, Err(Error::MissingLeadingBackslash), "Invalid '{}' JSON pointer", s);
267    }
268
269    #[test]
270    fn it_detects_root_json_pointer() -> Result<(), Error> {
271        let tests = [Pointer::new("")?, Pointer::root()];
272
273        for pointer in tests {
274            assert!(pointer.is_root(), "'{}' is a root JSON pointer", pointer);
275        }
276
277        Ok(())
278    }
279
280    #[test]
281    fn it_rejects_non_root_json_pointer() -> Result<(), Error> {
282        let tests = [
283            Pointer::new("/")?,
284            Pointer::new("/dummy_path/to/something")?,
285            Pointer::new("/0/1/2/3")?,
286        ];
287
288        for pointer in tests {
289            assert!(!pointer.is_root(), "'{}' is not a root JSON pointer", pointer);
290        }
291
292        Ok(())
293    }
294
295    #[test]
296    fn it_gets_parent_json_pointer() -> Result<(), Error> {
297        let tests = [
298            (Pointer::root(), None),
299            (Pointer::new("/")?, Some(Pointer::root())),
300            (Pointer::new("/key")?, Some(Pointer::new("")?)),
301            (Pointer::new("/nested/key")?, Some(Pointer::new("/nested")?)),
302            (
303                Pointer::new("/deeper/nested/key")?,
304                Some(Pointer::new("/deeper/nested")?),
305            ),
306        ];
307
308        for (pointer, expected_parent_pointer) in tests {
309            assert_eq!(
310                pointer.parent(),
311                expected_parent_pointer,
312                "Parent of '{}' JSON pointer",
313                pointer,
314            );
315        }
316
317        Ok(())
318    }
319
320    #[test]
321    fn it_gets_key_from_json_pointer() -> Result<(), Error> {
322        let tests = [
323            (Pointer::root(), None),
324            (Pointer::new("/")?, Some("")),
325            (Pointer::new("/key")?, Some("key")),
326            (Pointer::new("/nested/key")?, Some("key")),
327            (Pointer::new("/deeper/nested/key")?, Some("key")),
328            (Pointer::new("/with_encoded_char/~1key")?, Some("/key")),
329            (Pointer::new("/with_encoded_char/~0key")?, Some("~key")),
330            (Pointer::new("/with_encoded_char/~10key")?, Some("/0key")),
331            (Pointer::new("/with_encoded_char/~01key")?, Some("~1key")),
332        ];
333
334        for (pointer, expected_key) in tests {
335            let expected_key = expected_key.map(ToString::to_string);
336            assert_eq!(pointer.key(), expected_key, "Key of '{}' JSON pointer", pointer);
337        }
338
339        Ok(())
340    }
341
342    #[test]
343    fn it_detects_parent_json_pointer() -> Result<(), Error> {
344        let tests = [
345            (Pointer::root(), Pointer::new("/")?),
346            (Pointer::new("/")?, Pointer::new("//a")?),
347            (Pointer::new("/foo/0")?, Pointer::new("/foo/0/zoo")?),
348        ];
349
350        for (pointer_a, pointer_b) in tests {
351            assert!(
352                pointer_a.is_parent_of(&pointer_b),
353                "'{}' is the parent of '{}' JSON pointer",
354                pointer_a,
355                pointer_b
356            );
357        }
358
359        Ok(())
360    }
361
362    #[test]
363    fn it_detects_non_parent_json_pointer() -> Result<(), Error> {
364        let tests = [
365            (Pointer::root(), Pointer::root()),
366            (Pointer::new("/a/b")?, Pointer::new("/a")?),
367            (Pointer::new("/a/b")?, Pointer::new("/a/b")?),
368            (Pointer::new("/a/b")?, Pointer::new("/a/b/c/d")?),
369        ];
370
371        for (pointer_a, pointer_b) in tests {
372            assert!(
373                !pointer_a.is_parent_of(&pointer_b),
374                "'{}' is not the parent of '{}' JSON pointer",
375                pointer_a,
376                pointer_b,
377            );
378        }
379
380        Ok(())
381    }
382
383    #[test]
384    fn it_detects_ancestor_json_pointer() -> Result<(), Error> {
385        let tests = [
386            (Pointer::root(), Pointer::root()),
387            (Pointer::root(), Pointer::new("/")?),
388            (Pointer::new("/")?, Pointer::new("//a")?),
389            (Pointer::new("/a/b")?, Pointer::new("/a/b")?),
390            (Pointer::new("/a/b/c")?, Pointer::new("/a/b/c/d/e/f/g")?),
391            (Pointer::new("/foo/0")?, Pointer::new("/foo/0/bar/zoo")?),
392        ];
393
394        for (pointer_a, pointer_b) in tests {
395            assert!(
396                pointer_a.is_ancestor_of(&pointer_b),
397                "'{}' is an ancestor of '{}' JSON pointer",
398                pointer_a,
399                pointer_b
400            );
401        }
402
403        Ok(())
404    }
405
406    #[test]
407    fn it_detects_non_ancestor_json_pointer() -> Result<(), Error> {
408        let tests = [
409            (Pointer::new("/a/b")?, Pointer::new("/a")?),
410            (Pointer::new("/0/foo/bar/zoo")?, Pointer::new("/1/foo/bar/zoo")?),
411            (Pointer::new("/tric")?, Pointer::new("/tricky/test")?),
412        ];
413
414        for (pointer_a, pointer_b) in tests {
415            assert!(
416                !pointer_a.is_ancestor_of(&pointer_b),
417                "'{}' is not an ancestor of '{}' JSON pointer",
418                pointer_a,
419                pointer_b,
420            );
421        }
422
423        Ok(())
424    }
425
426    #[test]
427    fn it_detects_sibling_json_pointer() -> Result<(), Error> {
428        let tests = [
429            (Pointer::new("/")?, Pointer::new("/a")?),
430            (Pointer::new("/a")?, Pointer::new("/")?),
431            (Pointer::new("/a/b/c")?, Pointer::new("/a/b/d")?),
432            (Pointer::new("/foo/bar/zoo/0")?, Pointer::new("/foo/bar/zoo/42")?),
433        ];
434
435        for (pointer_a, pointer_b) in tests {
436            assert!(
437                pointer_a.is_sibling_of(&pointer_b),
438                "'{}' is a sibling of '{}' JSON pointer",
439                pointer_a,
440                pointer_b
441            );
442        }
443
444        Ok(())
445    }
446
447    #[test]
448    fn it_detects_non_sibling_json_pointer() -> Result<(), Error> {
449        let tests = [
450            (Pointer::root(), Pointer::root()),
451            (Pointer::new("/b/d")?, Pointer::new("/b/d")?),
452            (Pointer::new("/b/d")?, Pointer::new("/a")?),
453            (Pointer::new("/a")?, Pointer::new("/b/d")?),
454            (Pointer::new("/a/b/c")?, Pointer::new("/d/e/f")?),
455            (Pointer::new("/0/foo/bar/zoo")?, Pointer::new("/1/foo/bar/zoo")?),
456        ];
457
458        for (pointer_a, pointer_b) in tests {
459            assert!(
460                !pointer_a.is_sibling_of(&pointer_b),
461                "'{}' is not a sibling of '{}' JSON pointer",
462                pointer_a,
463                pointer_b
464            );
465        }
466
467        Ok(())
468    }
469
470    #[test]
471    fn it_gets_ancestor_json_pointers() -> Result<(), Error> {
472        let tests = [
473            (Pointer::root(), vec![Pointer::root()]),
474            (Pointer::new("/")?, vec![Pointer::new("/")?, Pointer::root()]),
475            (
476                Pointer::new("/a/b")?,
477                vec![Pointer::new("/a/b")?, Pointer::new("/a")?, Pointer::root()],
478            ),
479            (
480                Pointer::new("/0/foo/bar/zoo")?,
481                vec![
482                    Pointer::new("/0/foo/bar/zoo")?,
483                    Pointer::new("/0/foo/bar")?,
484                    Pointer::new("/0/foo")?,
485                    Pointer::new("/0")?,
486                    Pointer::root(),
487                ],
488            ),
489        ];
490
491        for (pointer, expected_ancestor_pointers) in tests {
492            let ancestor_pointers = pointer.ancestors().collect::<Vec<_>>();
493
494            assert_eq!(
495                ancestor_pointers, expected_ancestor_pointers,
496                "Ancestors of '{}' JSON pointer",
497                pointer
498            );
499        }
500
501        Ok(())
502    }
503
504    #[test]
505    fn it_gets_json_pointer_depth() -> Result<(), Error> {
506        let tests = [
507            (Pointer::root(), 0),
508            (Pointer::new("/")?, 1),
509            (Pointer::new("/a")?, 1),
510            (Pointer::new("/a/b/c")?, 3),
511            (Pointer::new("/foo/0/bar/1/zoo/2")?, 6),
512        ];
513
514        for (pointer, expected_depth) in tests {
515            assert_eq!(pointer.depth(), expected_depth, "Depth of '{}' JSON pointer", pointer);
516        }
517
518        Ok(())
519    }
520
521    #[test]
522    fn it_evaluates_json_pointer_into_tokens() -> Result<(), Error> {
523        let tests = [
524            (Pointer::root(), vec![]),
525            (Pointer::new("/")?, vec![""]),
526            (Pointer::new("/~1a")?, vec!["/a"]),
527            (Pointer::new("/~01a")?, vec!["~1a"]),
528            (Pointer::new("/~10a")?, vec!["/0a"]),
529            (Pointer::new("/~1a/~0b/c")?, vec!["/a", "~b", "c"]),
530        ];
531
532        for (pointer, expected_tokens) in tests {
533            let tokens = pointer.tokenize().collect::<Vec<_>>();
534            let tokens = tokens.iter().map(|s| s.as_str()).collect::<Vec<_>>();
535
536            assert_eq!(tokens, expected_tokens, "Tokens of '{}' JSON pointer", pointer);
537        }
538
539        Ok(())
540    }
541}