#![allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum JsonPointerError {
InvalidSyntax(String),
KeyNotFound(String),
IndexOutOfRange(usize),
InvalidIndex(String),
}
impl std::fmt::Display for JsonPointerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidSyntax(s) => write!(f, "invalid JSON pointer syntax: {s}"),
Self::KeyNotFound(k) => write!(f, "key not found: {k}"),
Self::IndexOutOfRange(i) => write!(f, "index out of range: {i}"),
Self::InvalidIndex(s) => write!(f, "invalid array index: {s}"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct JsonPointer {
tokens: Vec<String>,
}
impl JsonPointer {
pub fn parse(s: &str) -> Result<Self, JsonPointerError> {
if s.is_empty() {
return Ok(JsonPointer { tokens: vec![] });
}
if !s.starts_with('/') {
return Err(JsonPointerError::InvalidSyntax(s.to_string()));
}
let tokens = s[1..]
.split('/')
.map(|tok| tok.replace("~1", "/").replace("~0", "~"))
.collect();
Ok(JsonPointer { tokens })
}
pub fn tokens(&self) -> &[String] {
&self.tokens
}
pub fn is_root(&self) -> bool {
self.tokens.is_empty()
}
pub fn depth(&self) -> usize {
self.tokens.len()
}
pub fn to_string_repr(&self) -> String {
if self.tokens.is_empty() {
return String::new();
}
let mut out = String::new();
for tok in &self.tokens {
out.push('/');
out.push_str(&tok.replace('~', "~0").replace('/', "~1"));
}
out
}
}
pub fn escape_token(tok: &str) -> String {
tok.replace('~', "~0").replace('/', "~1")
}
pub fn unescape_token(tok: &str) -> String {
tok.replace("~1", "/").replace("~0", "~")
}
pub fn pointer_leaf(ptr: &JsonPointer) -> Option<&str> {
ptr.tokens().last().map(|s| s.as_str())
}
pub fn pointer_parent(ptr: &JsonPointer) -> Option<JsonPointer> {
if ptr.is_root() {
return None;
}
let tokens = ptr.tokens[..ptr.tokens.len().saturating_sub(1)].to_vec();
Some(JsonPointer { tokens })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_root() {
let p = JsonPointer::parse("").expect("should succeed");
assert!(p.is_root());
}
#[test]
fn test_parse_simple() {
let p = JsonPointer::parse("/foo/bar").expect("should succeed");
assert_eq!(p.tokens(), &["foo", "bar"]);
}
#[test]
fn test_escape_tilde() {
let p = JsonPointer::parse("/a~0b").expect("should succeed");
assert_eq!(p.tokens()[0], "a~b");
}
#[test]
fn test_escape_slash() {
let p = JsonPointer::parse("/a~1b").expect("should succeed");
assert_eq!(p.tokens()[0], "a/b");
}
#[test]
fn test_invalid_no_leading_slash() {
assert!(JsonPointer::parse("foo/bar").is_err());
}
#[test]
fn test_depth() {
let p = JsonPointer::parse("/a/b/c").expect("should succeed");
assert_eq!(p.depth(), 3);
}
#[test]
fn test_roundtrip() {
let s = "/foo~0bar/baz~1qux";
let p = JsonPointer::parse(s).expect("should succeed");
assert_eq!(p.to_string_repr(), s);
}
#[test]
fn test_leaf() {
let p = JsonPointer::parse("/x/y/z").expect("should succeed");
assert_eq!(pointer_leaf(&p), Some("z"));
}
#[test]
fn test_parent() {
let p = JsonPointer::parse("/a/b").expect("should succeed");
let parent = pointer_parent(&p).expect("should succeed");
assert_eq!(parent.tokens(), &["a"]);
}
#[test]
fn test_root_parent_none() {
let p = JsonPointer::parse("").expect("should succeed");
assert!(pointer_parent(&p).is_none());
}
}