use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Path(String);
impl Path {
pub fn root() -> Self {
Path(String::new())
}
pub fn field(&self, name: &str) -> Self {
if self.0.is_empty() {
Path(name.to_string())
} else {
let mut s = String::with_capacity(self.0.len() + 1 + name.len());
s.push_str(&self.0);
s.push('.');
s.push_str(name);
Path(s)
}
}
pub fn index(&self, idx: usize) -> Self {
use std::fmt::Write;
let mut s = String::with_capacity(self.0.len() + 10);
s.push_str(&self.0);
s.push('[');
write!(&mut s, "{}", idx).unwrap();
s.push(']');
Path(s)
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn to_json_pointer(&self) -> String {
if self.0.is_empty() {
return String::from("");
}
let mut result = String::from("/");
let mut chars = self.0.chars().peekable();
while let Some(ch) = chars.next() {
match ch {
'.' => result.push('/'),
'[' => {
result.push('/');
for c in chars.by_ref() {
if c == ']' {
break;
}
result.push(c);
}
}
_ => result.push(ch),
}
}
result
}
pub fn depth(&self) -> usize {
if self.0.is_empty() {
return 0;
}
let mut depth = 1;
for ch in self.0.chars() {
if ch == '.' || ch == '[' {
depth += 1;
}
}
depth
}
pub fn last_index(&self) -> Option<usize> {
if self.0.is_empty() {
return None;
}
if !self.0.ends_with(']') {
return None;
}
if let Some(start) = self.0.rfind('[') {
self.0[start + 1..self.0.len() - 1].parse().ok()
} else {
None
}
}
}
impl fmt::Display for Path {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl AsRef<str> for Path {
fn as_ref(&self) -> &str {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_root() {
let root = Path::root();
assert_eq!(root.as_str(), "");
assert_eq!(root.to_string(), "");
}
#[test]
fn test_single_field() {
let path = Path::root().field("user");
assert_eq!(path.as_str(), "user");
assert_eq!(path.to_string(), "user");
}
#[test]
fn test_nested_fields() {
let path = Path::root().field("user").field("profile").field("name");
assert_eq!(path.as_str(), "user.profile.name");
}
#[test]
fn test_array_index() {
let path = Path::root().field("items").index(3);
assert_eq!(path.as_str(), "items[3]");
}
#[test]
fn test_nested_array_index() {
let path = Path::root().field("items").index(0).field("id");
assert_eq!(path.as_str(), "items[0].id");
}
#[test]
fn test_multiple_indices() {
let path = Path::root().field("matrix").index(1).index(2);
assert_eq!(path.as_str(), "matrix[1][2]");
}
#[test]
fn test_json_pointer_simple() {
let path = Path::root().field("user").field("name");
assert_eq!(path.to_json_pointer(), "/user/name");
}
#[test]
fn test_json_pointer_with_index() {
let path = Path::root().field("items").index(0).field("value");
assert_eq!(path.to_json_pointer(), "/items/0/value");
}
#[test]
fn test_json_pointer_root() {
let path = Path::root();
assert_eq!(path.to_json_pointer(), "");
}
#[test]
fn test_depth_root() {
assert_eq!(Path::root().depth(), 0);
}
#[test]
fn test_depth_single() {
assert_eq!(Path::root().field("user").depth(), 1);
}
#[test]
fn test_depth_nested() {
assert_eq!(Path::root().field("a").field("b").field("c").depth(), 3);
}
#[test]
fn test_depth_with_index() {
assert_eq!(Path::root().field("items").index(0).depth(), 2);
}
#[test]
fn test_ordering() {
let p1 = Path::root().field("a");
let p2 = Path::root().field("b");
let p3 = Path::root().field("a").field("c");
assert!(p1 < p2);
assert!(p1 < p3);
assert!(p3 < p2); }
}