#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct InvalidJsonPointer;
#[derive(Clone, Copy, Debug)]
pub struct JsonPointer<'a>(&'a str);
impl<'a> JsonPointer<'a> {
pub fn parse(path: &'a str) -> Result<Self, InvalidJsonPointer> {
if !path.is_empty() && !path.starts_with('/') {
return Err(InvalidJsonPointer);
}
let mut bytes = path.bytes();
while let Some(byte) = bytes.next() {
if byte == b'~' && !matches!(bytes.next(), Some(b'0' | b'1')) {
return Err(InvalidJsonPointer);
}
}
Ok(Self(path))
}
pub fn segments(self) -> impl Iterator<Item = PointerSegment<'a>> {
self.0.split('/').skip(1).map(PointerSegment)
}
}
#[derive(Clone, Copy)]
pub struct PointerSegment<'a>(&'a str);
impl PointerSegment<'_> {
pub fn decode_into<'a>(self, target: &'a mut [u8]) -> Result<&'a str, InvalidJsonPointer> {
let mut chars = self.0.chars();
let mut used = 0;
while let Some(ch) = chars.next() {
let ch = if ch == '~' {
match chars.next() { Some('0') => '~', Some('1') => '/', _ => return Err(InvalidJsonPointer) }
} else { ch };
let mut bytes = [0; 4];
let bytes = ch.encode_utf8(&mut bytes).as_bytes();
let end = used + bytes.len();
target.get_mut(used..end).ok_or(InvalidJsonPointer)?.copy_from_slice(bytes);
used = end;
}
std::str::from_utf8(&target[..used]).map_err(|_| InvalidJsonPointer)
}
pub fn matches(self, key: &str) -> bool {
let mut encoded = self.0.chars();
let decoded = std::iter::from_fn(move || {
encoded.next().map(|ch| {
if ch == '~' {
match encoded.next() {
Some('0') => '~',
Some('1') => '/',
_ => unreachable!("validated JSON Pointer"),
}
} else {
ch
}
})
});
decoded.eq(key.chars())
}
pub fn array_index(self) -> Option<usize> {
let raw = self.0;
if raw.is_empty() || (raw.len() > 1 && raw.starts_with('0'))
|| !raw.bytes().all(|b| b.is_ascii_digit()) {
return None;
}
raw.parse().ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escaped_keys_empty_keys_and_canonical_array_indices() {
let path = JsonPointer::parse("/a~1b/~0/界//0").unwrap();
for (segment, key) in path.segments().zip(["a/b", "~", "界", "", "0"]) {
assert!(segment.matches(key));
}
assert_eq!(JsonPointer::parse("").unwrap().segments().count(), 0);
for path in ["a", "/~", "/~2", "/~~0"] {
assert!(JsonPointer::parse(path).is_err());
}
for key in ["00", "01", "-", "-1", "+1", "", "184467440737095516160"] {
assert!(PointerSegment(key).array_index().is_none());
}
assert_eq!(PointerSegment("0").array_index(), Some(0));
assert_eq!(PointerSegment("12").array_index(), Some(12));
}
}