use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Segment {
Key(Box<str>),
Index(usize),
}
impl Segment {
pub fn key(k: impl Into<Box<str>>) -> Segment {
Segment::Key(k.into())
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Path(pub Vec<Segment>);
impl Path {
pub fn root() -> Path {
Path(Vec::new())
}
pub fn push(&mut self, seg: Segment) {
self.0.push(seg);
}
pub fn pop(&mut self) -> Option<Segment> {
self.0.pop()
}
pub fn depth(&self) -> usize {
self.0.len()
}
pub fn segments(&self) -> &[Segment] {
&self.0
}
}
pub fn write_key(f: &mut fmt::Formatter<'_>, key: &str) -> fmt::Result {
let bare = !key.is_empty()
&& key
.chars()
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
&& key.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
if bare {
write!(f, ".{key}")
} else {
write!(f, ".{}", serde_json::Value::String(key.to_string()))
}
}
impl fmt::Display for Path {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.is_empty() {
return f.write_str(".");
}
for seg in &self.0 {
match seg {
Segment::Key(k) => write_key(f, k)?,
Segment::Index(i) => write!(f, "[{i}]")?,
}
}
Ok(())
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Step {
Property(Box<str>),
Index(usize),
EachIndex,
EachMember,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Selector(pub Vec<Step>);
impl Selector {
pub fn root() -> Selector {
Selector(Vec::new())
}
pub fn property(mut self, name: impl Into<Box<str>>) -> Selector {
self.0.push(Step::Property(name.into()));
self
}
pub fn index(mut self, i: usize) -> Selector {
self.0.push(Step::Index(i));
self
}
pub fn each_index(mut self) -> Selector {
self.0.push(Step::EachIndex);
self
}
pub fn each_member(mut self) -> Selector {
self.0.push(Step::EachMember);
self
}
pub fn compose(mut self, other: &Selector) -> Selector {
self.0.extend(other.0.iter().cloned());
self
}
pub fn from_segments(segments: &[Segment]) -> Selector {
Selector(
segments
.iter()
.map(|s| match s {
Segment::Key(k) => Step::Property(k.clone()),
Segment::Index(i) => Step::Index(*i),
})
.collect(),
)
}
pub fn steps(&self) -> &[Step] {
&self.0
}
pub fn is_root(&self) -> bool {
self.0.is_empty()
}
pub fn is_multi(&self) -> bool {
self.0
.iter()
.any(|s| matches!(s, Step::EachIndex | Step::EachMember))
}
pub fn may_overlap(&self, other: &Selector) -> bool {
let (short, long) = if self.0.len() <= other.0.len() {
(self, other)
} else {
(other, self)
};
short
.0
.iter()
.zip(long.0.iter())
.all(|(a, b)| step_may_match_same(a, b))
}
}
fn step_may_match_same(a: &Step, b: &Step) -> bool {
match (a, b) {
(Step::Property(x), Step::Property(y)) => x == y,
(Step::Property(_), Step::EachMember) | (Step::EachMember, Step::Property(_)) => true,
(Step::EachMember, Step::EachMember) => true,
(Step::Index(x), Step::Index(y)) => x == y,
(Step::Index(_), Step::EachIndex) | (Step::EachIndex, Step::Index(_)) => true,
(Step::EachIndex, Step::EachIndex) => true,
_ => false,
}
}
impl fmt::Display for Selector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0.is_empty() {
return f.write_str(".");
}
for step in &self.0 {
match step {
Step::Property(k) => write_key(f, k)?,
Step::Index(i) => write!(f, "[{i}]")?,
Step::EachIndex => f.write_str("[*]")?,
Step::EachMember => f.write_str("[]")?,
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_is_jq() {
let s = Selector::root()
.property("response")
.property("odd key")
.index(3)
.each_index()
.each_member();
assert_eq!(s.to_string(), ".response.\"odd key\"[3][*][]");
assert_eq!(Selector::root().to_string(), ".");
let p = Path(vec![
Segment::key("a"),
Segment::Index(0),
Segment::key("b-c"),
]);
assert_eq!(p.to_string(), ".a[0].\"b-c\"");
assert_eq!(Path::root().to_string(), ".");
}
#[test]
fn from_segments_is_single() {
let s = Selector::from_segments(&[Segment::key("account"), Segment::key("balance")]);
assert_eq!(s.to_string(), ".account.balance");
assert!(!s.is_multi());
assert!(Selector::root().each_index().is_multi());
}
#[test]
fn overlap() {
let rows = Selector::root().property("records").each_index();
let meta = Selector::root().property("metadata");
let inner = Selector::root().property("records").index(2).property("x");
assert!(!rows.may_overlap(&meta));
assert!(rows.may_overlap(&inner));
assert!(inner.may_overlap(&rows));
assert!(rows.may_overlap(&rows));
assert!(Selector::root().may_overlap(&meta));
assert!(Selector::root().each_member().may_overlap(&meta));
assert!(!Selector::root().each_index().may_overlap(&meta));
}
#[test]
fn compose_appends() {
let a = Selector::root().property("a");
let b = Selector::root().each_index();
assert_eq!(a.compose(&b).to_string(), ".a[*]");
}
}