saddle_core/
json_pointer.rs1#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub struct InvalidJsonPointer;
6
7#[derive(Clone, Copy, Debug)]
8pub struct JsonPointer<'a>(&'a str);
9
10impl<'a> JsonPointer<'a> {
11 pub fn parse(path: &'a str) -> Result<Self, InvalidJsonPointer> {
12 if !path.is_empty() && !path.starts_with('/') {
13 return Err(InvalidJsonPointer);
14 }
15 let mut bytes = path.bytes();
16 while let Some(byte) = bytes.next() {
17 if byte == b'~' && !matches!(bytes.next(), Some(b'0' | b'1')) {
18 return Err(InvalidJsonPointer);
19 }
20 }
21 Ok(Self(path))
22 }
23
24 pub fn segments(self) -> impl Iterator<Item = PointerSegment<'a>> {
25 self.0.split('/').skip(1).map(PointerSegment)
26 }
27}
28
29#[derive(Clone, Copy)]
30pub struct PointerSegment<'a>(&'a str);
31
32impl PointerSegment<'_> {
33 pub fn decode_into<'a>(self, target: &'a mut [u8]) -> Result<&'a str, InvalidJsonPointer> {
34 let mut chars = self.0.chars();
35 let mut used = 0;
36 while let Some(ch) = chars.next() {
37 let ch = if ch == '~' {
38 match chars.next() { Some('0') => '~', Some('1') => '/', _ => return Err(InvalidJsonPointer) }
39 } else { ch };
40 let mut bytes = [0; 4];
41 let bytes = ch.encode_utf8(&mut bytes).as_bytes();
42 let end = used + bytes.len();
43 target.get_mut(used..end).ok_or(InvalidJsonPointer)?.copy_from_slice(bytes);
44 used = end;
45 }
46 std::str::from_utf8(&target[..used]).map_err(|_| InvalidJsonPointer)
47 }
48 pub fn matches(self, key: &str) -> bool {
49 let mut encoded = self.0.chars();
50 let decoded = std::iter::from_fn(move || {
51 encoded.next().map(|ch| {
52 if ch == '~' {
53 match encoded.next() {
54 Some('0') => '~',
55 Some('1') => '/',
56 _ => unreachable!("validated JSON Pointer"),
57 }
58 } else {
59 ch
60 }
61 })
62 });
63 decoded.eq(key.chars())
64 }
65
66 pub fn array_index(self) -> Option<usize> {
67 let raw = self.0;
68 if raw.is_empty() || (raw.len() > 1 && raw.starts_with('0'))
69 || !raw.bytes().all(|b| b.is_ascii_digit()) {
70 return None;
71 }
72 raw.parse().ok()
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn escaped_keys_empty_keys_and_canonical_array_indices() {
82 let path = JsonPointer::parse("/a~1b/~0/界//0").unwrap();
83 for (segment, key) in path.segments().zip(["a/b", "~", "界", "", "0"]) {
84 assert!(segment.matches(key));
85 }
86 assert_eq!(JsonPointer::parse("").unwrap().segments().count(), 0);
87 for path in ["a", "/~", "/~2", "/~~0"] {
88 assert!(JsonPointer::parse(path).is_err());
89 }
90 for key in ["00", "01", "-", "-1", "+1", "", "184467440737095516160"] {
91 assert!(PointerSegment(key).array_index().is_none());
92 }
93 assert_eq!(PointerSegment("0").array_index(), Some(0));
94 assert_eq!(PointerSegment("12").array_index(), Some(12));
95 }
96}