ssi_core/
json_pointer.rs

1use core::{fmt, ops::Deref, str::FromStr};
2use std::borrow::{Borrow, Cow};
3
4use serde::{Deserialize, Serialize};
5
6use crate::BytesBuf;
7
8#[macro_export]
9macro_rules! json_pointer {
10    ($value:literal) => {
11        const {
12            match $crate::JsonPointer::from_str_const($value) {
13                Ok(p) => p,
14                Err(_) => panic!("invalid JSON pointer"),
15            }
16        }
17    };
18}
19
20#[derive(Debug, Clone, Copy, thiserror::Error)]
21#[error("invalid JSON pointer `{0}`")]
22pub struct InvalidJsonPointer<T = String>(pub T);
23
24/// JSON Pointer.
25///
26/// See: <https://datatracker.ietf.org/doc/html/rfc6901>
27#[derive(Debug, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
28#[repr(transparent)]
29pub struct JsonPointer(str);
30
31impl Default for &JsonPointer {
32    fn default() -> Self {
33        JsonPointer::ROOT
34    }
35}
36
37impl JsonPointer {
38    pub const ROOT: &'static Self = json_pointer!("");
39
40    /// Converts the given string into a JSON pointer.
41    pub fn new<S>(s: &S) -> Result<&Self, InvalidJsonPointer<&S>>
42    where
43        S: AsRef<[u8]> + ?Sized,
44    {
45        core::str::from_utf8(s.as_ref())
46            .ok()
47            .and_then(|s| Self::from_str_const(s).ok())
48            .ok_or(InvalidJsonPointer(s))
49    }
50
51    pub const fn from_str_const(s: &str) -> Result<&Self, InvalidJsonPointer<&str>> {
52        if Self::validate_str(s) {
53            Ok(unsafe { Self::new_unchecked_str(s) })
54        } else {
55            Err(InvalidJsonPointer(s))
56        }
57    }
58
59    /// Converts the given string into a JSON pointer without validation.
60    ///
61    /// # Safety
62    ///
63    /// The input string *must* be a valid JSON pointer.
64    pub const unsafe fn new_unchecked_str(s: &str) -> &Self {
65        std::mem::transmute(s)
66    }
67
68    /// Converts the given string into a JSON pointer without validation.
69    ///
70    /// # Safety
71    ///
72    /// The input string *must* be a valid JSON pointer.
73    pub const unsafe fn new_unchecked(s: &[u8]) -> &Self {
74        Self::new_unchecked_str(core::str::from_utf8_unchecked(s))
75    }
76
77    /// Confirms the validity of a string such that it may be safely used for
78    /// [`Self::new_unchecked`].
79    pub const fn validate_bytes(s: &[u8]) -> bool {
80        match core::str::from_utf8(s) {
81            Ok(s) => Self::validate_str(s),
82            Err(_) => false,
83        }
84    }
85
86    /// Confirms the validity of a string such that it may be safely used for
87    /// [`Self::new_unchecked_str`].
88    pub const fn validate_str(s: &str) -> bool {
89        let bytes = s.as_bytes();
90
91        if !matches!(bytes, [] | [b'/', ..]) {
92            return false;
93        }
94
95        let mut i = 0;
96        while i < bytes.len() {
97            // Escape char.
98            if bytes[i] == b'~' {
99                i += 1;
100                if i >= bytes.len() || !matches!(bytes[i], b'0' | b'1') {
101                    return false;
102                }
103            }
104
105            i += 1
106        }
107
108        true
109    }
110
111    pub fn as_bytes(&self) -> &[u8] {
112        self.0.as_bytes()
113    }
114
115    pub fn as_str(&self) -> &str {
116        &self.0
117    }
118
119    pub fn is_empty(&self) -> bool {
120        self.0.is_empty()
121    }
122
123    pub fn split_first(&self) -> Option<(&ReferenceToken, &Self)> {
124        self.0.strip_prefix("/").map(|s| {
125            let (left, right) = s.find("/").map(|idx| s.split_at(idx)).unwrap_or((s, ""));
126            // Safety: the token is guaranteed not to include a '/', and remaining shall be either
127            // empty or a valid pointer starting with '/'.
128            let token = unsafe { ReferenceToken::new_unchecked(left) };
129            let remaining = unsafe { Self::new_unchecked_str(right) };
130            (token, remaining)
131        })
132    }
133
134    pub fn iter(&self) -> JsonPointerIter {
135        let mut tokens = self.0.split('/');
136        tokens.next();
137        JsonPointerIter(tokens)
138    }
139}
140
141impl ToOwned for JsonPointer {
142    type Owned = JsonPointerBuf;
143
144    fn to_owned(&self) -> Self::Owned {
145        JsonPointerBuf(self.0.to_owned())
146    }
147}
148
149impl AsRef<JsonPointer> for JsonPointer {
150    fn as_ref(&self) -> &JsonPointer {
151        self
152    }
153}
154
155impl fmt::Display for JsonPointer {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        self.as_str().fmt(f)
158    }
159}
160
161impl<'a> IntoIterator for &'a JsonPointer {
162    type Item = &'a ReferenceToken;
163    type IntoIter = JsonPointerIter<'a>;
164
165    fn into_iter(self) -> Self::IntoIter {
166        self.iter()
167    }
168}
169
170pub struct JsonPointerIter<'a>(std::str::Split<'a, char>);
171
172impl<'a> Iterator for JsonPointerIter<'a> {
173    type Item = &'a ReferenceToken;
174
175    fn next(&mut self) -> Option<Self::Item> {
176        self.0.next().map(|s| unsafe { std::mem::transmute(s) })
177    }
178}
179
180impl<'de> Deserialize<'de> for &'de JsonPointer {
181    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
182    where
183        D: serde::Deserializer<'de>,
184    {
185        let s: &str = <&str as Deserialize>::deserialize(deserializer)?;
186        JsonPointer::new(s).map_err(serde::de::Error::custom)
187    }
188}
189
190/// JSON Pointer buffer.
191///
192/// See: <https://datatracker.ietf.org/doc/html/rfc6901>
193#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
194pub struct JsonPointerBuf(String);
195
196impl Default for JsonPointerBuf {
197    fn default() -> Self {
198        JsonPointer::ROOT.to_owned()
199    }
200}
201
202impl JsonPointerBuf {
203    /// Converts the given byte string into an owned JSON pointer.
204    pub fn new<B: BytesBuf>(value: B) -> Result<Self, InvalidJsonPointer<B>> {
205        if JsonPointer::validate_bytes(value.as_ref()) {
206            let v: Vec<u8> = value.into();
207            // SAFETY: we've just ensured the contents of the BytesBuf is a valid UTF-8 string and
208            // JsonPointer.
209            Ok(Self(unsafe { String::from_utf8_unchecked(v) }))
210        } else {
211            Err(InvalidJsonPointer(value))
212        }
213    }
214
215    pub fn push(&mut self, token: &str) {
216        self.0.reserve(1 + token.len());
217        self.0.push('/');
218        for c in token.chars() {
219            match c {
220                '~' => self.0.push_str("~0"),
221                '/' => self.0.push_str("~1"),
222                _ => self.0.push(c),
223            }
224        }
225    }
226
227    pub fn push_index(&mut self, i: usize) {
228        use core::fmt::Write;
229        write!(self.0, "/{i}").unwrap()
230    }
231
232    pub fn as_json_pointer(&self) -> &JsonPointer {
233        unsafe {
234            // SAFETY: the inner bytes are representing a JSON pointer by
235            // construction.
236            JsonPointer::new_unchecked_str(&self.0)
237        }
238    }
239}
240
241impl Deref for JsonPointerBuf {
242    type Target = JsonPointer;
243
244    fn deref(&self) -> &Self::Target {
245        self.as_json_pointer()
246    }
247}
248
249impl Borrow<JsonPointer> for JsonPointerBuf {
250    fn borrow(&self) -> &JsonPointer {
251        self.as_json_pointer()
252    }
253}
254
255impl AsRef<JsonPointer> for JsonPointerBuf {
256    fn as_ref(&self) -> &JsonPointer {
257        self.as_json_pointer()
258    }
259}
260
261impl FromStr for JsonPointerBuf {
262    type Err = InvalidJsonPointer;
263
264    fn from_str(s: &str) -> Result<Self, Self::Err> {
265        s.to_owned().try_into()
266    }
267}
268
269impl TryFrom<String> for JsonPointerBuf {
270    type Error = InvalidJsonPointer;
271
272    fn try_from(value: String) -> Result<Self, Self::Error> {
273        if JsonPointer::validate_str(&value) {
274            Ok(Self(value))
275        } else {
276            Err(InvalidJsonPointer(value))
277        }
278    }
279}
280
281impl fmt::Display for JsonPointerBuf {
282    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283        self.as_str().fmt(f)
284    }
285}
286
287impl<'de> Deserialize<'de> for JsonPointerBuf {
288    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
289    where
290        D: serde::Deserializer<'de>,
291    {
292        String::deserialize(deserializer)?
293            .try_into()
294            .map_err(serde::de::Error::custom)
295    }
296}
297
298#[derive(Debug)]
299#[repr(transparent)]
300pub struct ReferenceToken(str);
301
302impl ReferenceToken {
303    /// Converts the given string into a JSON pointer reference token without
304    /// validation.
305    ///
306    /// # Safety
307    ///
308    /// The input string *must* be a valid JSON pointer reference token.
309    pub const unsafe fn new_unchecked(s: &str) -> &Self {
310        std::mem::transmute(s)
311    }
312
313    pub fn is_escaped(&self) -> bool {
314        self.0.contains("~")
315    }
316
317    pub fn as_bytes(&self) -> &[u8] {
318        self.0.as_bytes()
319    }
320
321    pub fn as_str(&self) -> &str {
322        &self.0
323    }
324
325    pub fn to_decoded(&self) -> Cow<str> {
326        if self.is_escaped() {
327            Cow::Owned(self.decode())
328        } else {
329            Cow::Borrowed(self.as_str())
330        }
331    }
332
333    pub fn decode(&self) -> String {
334        let mut buf = String::with_capacity(self.0.len());
335        let mut chars = self.0.chars();
336        buf.extend(core::iter::from_fn(|| {
337            Some(match chars.next()? {
338                '~' => match chars.next() {
339                    Some('0') => '~',
340                    Some('1') => '/',
341                    _ => unreachable!(),
342                },
343                c => c,
344            })
345        }));
346        buf
347    }
348
349    pub fn as_array_index(&self) -> Option<usize> {
350        // Like usize::from_str, but don't allow leading '+' or '0'.
351        match self.0.as_bytes() {
352            [c @ b'0'..=b'9'] => Some((c - b'0') as usize),
353            [b'1'..=b'9', ..] => self.0.parse().ok(),
354            _ => None,
355        }
356    }
357}
358
359impl fmt::Display for ReferenceToken {
360    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361        self.as_str().fmt(f)
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368
369    #[test]
370    fn test_serde_borrow() {
371        let s = String::from("\"/foo/b~1ar\"");
372        let p: JsonPointerBuf = serde_json::from_str(&s).unwrap();
373        let jp: &JsonPointer = serde_json::from_str(&s).unwrap();
374        assert_eq!(p.0, jp.0);
375
376        serde_json::from_str::<&JsonPointer>("\"invalid\"").unwrap_err();
377    }
378}