use crate::prelude::*;
use core::fmt::Display;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
pub enum Path<'a> {
#[default]
Root,
Seq {
parent: &'a Path<'a>,
index: usize,
},
Map {
parent: &'a Path<'a>,
key: &'a str,
},
Alias {
parent: &'a Path<'a>,
},
Unknown {
parent: &'a Path<'a>,
},
}
impl<'a> Path<'a> {
#[must_use]
pub fn index(&'a self, index: usize) -> Path<'a> {
Path::Seq {
parent: self,
index,
}
}
#[must_use]
pub fn key(&'a self, key: &'a str) -> Path<'a> {
Path::Map { parent: self, key }
}
#[must_use]
pub fn alias(&'a self) -> Path<'a> {
Path::Alias { parent: self }
}
#[must_use]
pub fn unknown(&'a self) -> Path<'a> {
Path::Unknown { parent: self }
}
#[must_use]
pub fn is_root(&self) -> bool {
matches!(self, Path::Root)
}
#[must_use]
pub fn parent(&self) -> Option<&Path<'a>> {
match self {
Path::Root => None,
Path::Seq { parent, .. }
| Path::Map { parent, .. }
| Path::Alias { parent }
| Path::Unknown { parent } => Some(parent),
}
}
#[must_use]
pub fn depth(&self) -> usize {
match self {
Path::Root => 0,
Path::Seq { parent, .. }
| Path::Map { parent, .. }
| Path::Alias { parent }
| Path::Unknown { parent } => 1 + parent.depth(),
}
}
}
impl Display for Path<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
struct Parent<'a>(&'a Path<'a>);
impl Display for Parent<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Path::Root => Ok(()),
path => write!(f, "{}.", path),
}
}
}
struct ParentNoDot<'a>(&'a Path<'a>);
impl Display for ParentNoDot<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Path::Root => Ok(()),
path => write!(f, "{}", path),
}
}
}
match self {
Path::Root => f.write_str("."),
Path::Seq { parent, index } => {
write!(f, "{}[{}]", ParentNoDot(parent), index)
}
Path::Map { parent, key } => {
write!(f, "{}{}", Parent(parent), key)
}
Path::Alias { parent } => {
write!(f, "{}", Parent(parent))
}
Path::Unknown { parent } => {
write!(f, "{}?", Parent(parent))
}
}
}
}
#[derive(Debug, Clone)]
pub(crate) enum QuerySegment {
Key(String),
Index(usize),
Wildcard,
RecursiveDescent,
}
pub(crate) fn parse_query_path(path: &str) -> Vec<QuerySegment> {
let mut segments = Vec::new();
let mut current = String::new();
let mut chars = path.chars().peekable();
while let Some(c) = chars.next() {
match c {
'.' => {
if !current.is_empty() {
segments.push(QuerySegment::Key(core::mem::take(&mut current)));
}
if chars.peek() == Some(&'.') {
let _ = chars.next();
segments.push(QuerySegment::RecursiveDescent);
}
}
'[' => {
if !current.is_empty() {
segments.push(QuerySegment::Key(core::mem::take(&mut current)));
}
let mut index_str = String::new();
while let Some(&c) = chars.peek() {
if c == ']' {
let _ = chars.next();
break;
}
index_str.push(c);
let _ = chars.next();
}
if index_str == "*" {
segments.push(QuerySegment::Wildcard);
} else if let Ok(idx) = index_str.parse::<usize>() {
segments.push(QuerySegment::Index(idx));
}
}
']' => {}
'*' => {
if !current.is_empty() {
segments.push(QuerySegment::Key(core::mem::take(&mut current)));
}
segments.push(QuerySegment::Wildcard);
}
_ => {
current.push(c);
}
}
}
if !current.is_empty() {
segments.push(QuerySegment::Key(current));
}
segments
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_root_display() {
let root = Path::Root;
assert_eq!(root.to_string(), ".");
}
#[test]
fn test_path_map_display() {
let root = Path::Root;
let key1 = Path::Map {
parent: &root,
key: "config",
};
assert_eq!(key1.to_string(), "config");
let key2 = Path::Map {
parent: &key1,
key: "server",
};
assert_eq!(key2.to_string(), "config.server");
let key3 = Path::Map {
parent: &key2,
key: "host",
};
assert_eq!(key3.to_string(), "config.server.host");
}
#[test]
fn test_path_seq_display() {
let root = Path::Root;
let items = Path::Map {
parent: &root,
key: "items",
};
let first = Path::Seq {
parent: &items,
index: 0,
};
assert_eq!(first.to_string(), "items[0]");
let second = Path::Seq {
parent: &items,
index: 1,
};
assert_eq!(second.to_string(), "items[1]");
}
#[test]
fn test_path_mixed_display() {
let root = Path::Root;
let servers = Path::Map {
parent: &root,
key: "servers",
};
let first = Path::Seq {
parent: &servers,
index: 0,
};
let host = Path::Map {
parent: &first,
key: "host",
};
assert_eq!(host.to_string(), "servers[0].host");
}
#[test]
fn test_path_unknown_display() {
let root = Path::Root;
let unknown = Path::Unknown { parent: &root };
assert_eq!(unknown.to_string(), "?");
let key = Path::Map {
parent: &root,
key: "test",
};
let unknown2 = Path::Unknown { parent: &key };
assert_eq!(unknown2.to_string(), "test.?");
}
#[test]
fn test_path_builder_methods() {
let root = Path::Root;
let config = root.key("config");
let items = config.key("items");
let first = items.index(0);
let name = first.key("name");
assert_eq!(name.to_string(), "config.items[0].name");
}
#[test]
fn test_path_is_root() {
let root = Path::Root;
assert!(root.is_root());
let child = root.key("test");
assert!(!child.is_root());
}
#[test]
fn test_path_parent() {
let root = Path::Root;
assert!(root.parent().is_none());
let child = Path::Map {
parent: &root,
key: "test",
};
assert_eq!(child.parent(), Some(&root));
}
#[test]
fn test_path_depth() {
let root = Path::Root;
assert_eq!(root.depth(), 0);
let d1 = Path::Map {
parent: &root,
key: "a",
};
assert_eq!(d1.depth(), 1);
let d2 = Path::Map {
parent: &d1,
key: "b",
};
assert_eq!(d2.depth(), 2);
let d3 = Path::Seq {
parent: &d2,
index: 0,
};
assert_eq!(d3.depth(), 3);
}
#[test]
fn test_path_default() {
let path: Path = Path::default();
assert!(path.is_root());
}
#[test]
fn test_path_equality() {
let root1 = Path::Root;
let root2 = Path::Root;
assert_eq!(root1, root2);
let key1 = Path::Map {
parent: &root1,
key: "test",
};
let key2 = Path::Map {
parent: &root2,
key: "test",
};
assert_eq!(key1, key2);
let key3 = Path::Map {
parent: &root1,
key: "other",
};
assert_ne!(key1, key3);
}
#[test]
fn test_path_alias() {
let root = Path::Root;
let alias = root.alias();
assert!(matches!(alias, Path::Alias { .. }));
}
#[test]
fn test_path_complex_real_world() {
let root = Path::Root;
let deps = root.key("dependencies");
let serde = deps.key("serde");
let features = serde.key("features");
let first_feature = features.index(0);
assert_eq!(first_feature.to_string(), "dependencies.serde.features[0]");
}
#[test]
fn test_path_deeply_nested() {
let root = Path::Root;
let a = root.key("a");
let b = a.key("b");
let c = b.key("c");
let d = c.key("d");
let e = d.key("e");
assert_eq!(e.to_string(), "a.b.c.d.e");
assert_eq!(e.depth(), 5);
}
#[test]
fn parse_query_path_handles_inline_star_after_key() {
let segments = parse_query_path("field*");
assert_eq!(segments.len(), 2);
assert!(matches!(&segments[0], QuerySegment::Key(s) if s == "field"));
assert!(matches!(&segments[1], QuerySegment::Wildcard));
}
#[test]
fn parse_query_path_drops_unparseable_bracket_content() {
let segments = parse_query_path("items[abc]");
assert_eq!(segments.len(), 1);
assert!(matches!(&segments[0], QuerySegment::Key(s) if s == "items"));
}
#[test]
fn parse_query_path_handles_standalone_star_segment() {
let segments = parse_query_path("*");
assert_eq!(segments.len(), 1);
assert!(matches!(&segments[0], QuerySegment::Wildcard));
}
#[test]
fn parse_query_path_handles_recursive_descent() {
let segments = parse_query_path("..name");
assert_eq!(segments.len(), 2);
assert!(matches!(&segments[0], QuerySegment::RecursiveDescent));
assert!(matches!(&segments[1], QuerySegment::Key(s) if s == "name"));
}
}