miniconf/
jsonpath.rs

1use core::{
2    fmt::Write,
3    ops::{ControlFlow::*, Deref, DerefMut},
4};
5
6use serde::{Deserialize, Serialize};
7
8use crate::{IntoKeys, KeysIter, Node, Transcode, Traversal, TreeKey};
9
10/// JSON style path notation iterator
11///
12/// This is only styled after JSON notation, it does not adhere to it.
13/// Supported are both dot and key notation with and without
14/// names enclosed by `'` as well as various mixtures:
15///
16/// ```
17/// use miniconf::JsonPathIter;
18/// let path = ["foo", "bar", "4", "baz", "5", "6"];
19/// for valid in [
20///     ".foo.bar[4].baz[5][6]",
21///     "['foo']['bar'][4]['baz'][5][6]",
22///     ".foo['bar'].4.'baz'['5'].'6'",
23/// ] {
24///     assert_eq!(&path[..], JsonPathIter::from(valid).collect::<Vec<_>>());
25/// }
26///
27/// for short in ["'", "[", "['"] {
28///     assert!(JsonPathIter::from(short).next().is_none());
29/// }
30/// ```
31///
32/// # Limitations
33///
34/// * No attempt at validating conformance
35/// * Does not support any escaping
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize, Hash)]
37#[repr(transparent)]
38#[serde(transparent)]
39pub struct JsonPathIter<'a>(&'a str);
40
41impl core::fmt::Display for JsonPathIter<'_> {
42    #[inline]
43    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44        self.0.fmt(f)
45    }
46}
47
48impl<'a, T> From<&'a T> for JsonPathIter<'a>
49where
50    T: AsRef<str> + ?Sized,
51{
52    #[inline]
53    fn from(value: &'a T) -> Self {
54        Self(value.as_ref())
55    }
56}
57
58impl<'a> From<JsonPathIter<'a>> for &'a str {
59    #[inline]
60    fn from(value: JsonPathIter<'a>) -> Self {
61        value.0
62    }
63}
64
65impl<'a> Iterator for JsonPathIter<'a> {
66    type Item = &'a str;
67
68    fn next(&mut self) -> Option<Self::Item> {
69        for (open, close) in [
70            (".'", Continue("'")),    // "'" inclusive
71            (".", Break(['.', '['])), // '.' or '[' exclusive
72            ("['", Continue("']")),   // "']" inclusive
73            ("[", Continue("]")),     // "]" inclusive
74        ] {
75            if let Some(rest) = self.0.strip_prefix(open) {
76                let (end, sep) = match close {
77                    Break(close) => (rest.find(close).unwrap_or(rest.len()), 0),
78                    Continue(close) => (rest.find(close)?, close.len()),
79                };
80                let (next, rest) = rest.split_at(end);
81                self.0 = &rest[sep..];
82                return Some(next);
83            }
84        }
85        None
86    }
87}
88
89impl core::iter::FusedIterator for JsonPathIter<'_> {}
90
91/// JSON style path notation
92///
93/// `T` can be `Write` for `Transcode` with the following behavior:
94/// * Named fields (struct) are encoded in dot notation.
95/// * Indices (tuple struct, array) are encoded in index notation
96///
97/// `T` can be `AsRef<str>` for `IntoKeys` with the behavior described in [`JsonPathIter`].
98#[derive(
99    Clone, Copy, Debug, Default, PartialEq, Eq, Ord, PartialOrd, Serialize, Deserialize, Hash,
100)]
101#[repr(transparent)]
102#[serde(transparent)]
103pub struct JsonPath<T: ?Sized>(pub T);
104
105impl<T> From<T> for JsonPath<T> {
106    #[inline]
107    fn from(value: T) -> Self {
108        Self(value)
109    }
110}
111
112impl<T> JsonPath<T> {
113    /// Extract the inner value
114    #[inline]
115    pub fn into_inner(self) -> T {
116        self.0
117    }
118}
119
120impl<T: ?Sized> Deref for JsonPath<T> {
121    type Target = T;
122    #[inline]
123    fn deref(&self) -> &Self::Target {
124        &self.0
125    }
126}
127
128impl<T: ?Sized> DerefMut for JsonPath<T> {
129    #[inline]
130    fn deref_mut(&mut self) -> &mut Self::Target {
131        &mut self.0
132    }
133}
134
135impl<T: core::fmt::Display> core::fmt::Display for JsonPath<T> {
136    #[inline]
137    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
138        self.0.fmt(f)
139    }
140}
141
142impl<'a, T: AsRef<str> + ?Sized> IntoKeys for &'a JsonPath<T> {
143    type IntoKeys = KeysIter<JsonPathIter<'a>>;
144    #[inline]
145    fn into_keys(self) -> Self::IntoKeys {
146        JsonPathIter::from(self.0.as_ref()).into_keys()
147    }
148}
149
150impl<T: Write + ?Sized> Transcode for JsonPath<T> {
151    fn transcode<M, K>(&mut self, keys: K) -> Result<Node, Traversal>
152    where
153        M: TreeKey + ?Sized,
154        K: IntoKeys,
155    {
156        M::traverse_by_key(keys.into_keys(), |index, name, _len| {
157            match name {
158                Some(name) => {
159                    debug_assert!(!name.contains(['.', '\'', '[', ']']));
160                    self.0.write_char('.').and_then(|()| self.0.write_str(name))
161                }
162                None => self
163                    .0
164                    .write_char('[')
165                    .and_then(|()| self.0.write_str(itoa::Buffer::new().format(index)))
166                    .and_then(|()| self.0.write_char(']')),
167            }
168            .or(Err(()))
169        })
170        .try_into()
171    }
172}