miniconf/
jsonpath.rs

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