pub type Selector = Vec<Segment>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Segment {
Key(String),
Index(usize),
Wildcard,
Predicate { key: String, value: String },
}
pub fn parse(input: &str) -> Result<Selector, String> {
let mut segments = Vec::new();
let bytes = input.as_bytes();
let len = bytes.len();
let mut i = 0;
while i < len {
if bytes[i] == b'.' {
i += 1;
continue;
}
if bytes[i] == b'[' {
i += 1; let start = i;
let mut depth = 1u32;
while i < len && depth > 0 {
if bytes[i] == b'[' {
depth += 1;
} else if bytes[i] == b']' {
depth -= 1;
}
if depth > 0 {
i += 1;
}
}
if depth > 0 {
return Err("unclosed bracket in selector".to_string());
}
let content = &input[start..i];
i += 1;
if content == "*" {
segments.push(Segment::Wildcard);
} else if let Some(eq_pos) = content.find('=') {
let key = content[..eq_pos].to_string();
let value = content[eq_pos + 1..].to_string();
if key.is_empty() {
return Err("empty predicate key".to_string());
}
if let Some(stripped) = key.strip_prefix('?') {
return Err(format!(
"predicate key starts with '?'; use [{stripped}={value}] instead of [{key}={value}]"
));
}
segments.push(Segment::Predicate { key, value });
} else if let Ok(idx) = content.parse::<usize>() {
segments.push(Segment::Index(idx));
} else {
return Err(format!("invalid bracket content: {content}"));
}
} else {
let start = i;
while i < len && bytes[i] != b'.' && bytes[i] != b'[' {
i += 1;
}
let key = &input[start..i];
if !key.is_empty() {
segments.push(Segment::Key(key.to_string()));
}
}
}
Ok(segments)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_simple_key_path() {
let sel = parse("scripts.test").unwrap();
assert_eq!(
sel,
vec![Segment::Key("scripts".into()), Segment::Key("test".into()),]
);
}
#[test]
fn parse_array_index() {
let sel = parse("jobs[0].name").unwrap();
assert_eq!(
sel,
vec![
Segment::Key("jobs".into()),
Segment::Index(0),
Segment::Key("name".into()),
]
);
}
#[test]
fn parse_predicate() {
let sel = parse("jobs[id=test].timeout").unwrap();
assert_eq!(
sel,
vec![
Segment::Key("jobs".into()),
Segment::Predicate {
key: "id".into(),
value: "test".into(),
},
Segment::Key("timeout".into()),
]
);
}
#[test]
fn parse_wildcard() {
let sel = parse("steps[*].name").unwrap();
assert_eq!(
sel,
vec![
Segment::Key("steps".into()),
Segment::Wildcard,
Segment::Key("name".into()),
]
);
}
#[test]
fn parse_unclosed_bracket_returns_error() {
let err = parse("items[0").unwrap_err();
assert!(
err.contains("unclosed bracket"),
"expected 'unclosed bracket', got: {err}"
);
}
#[test]
fn parse_empty_predicate_key_returns_error() {
let err = parse("items[=value]").unwrap_err();
assert!(
err.contains("empty predicate key"),
"expected 'empty predicate key', got: {err}"
);
}
#[test]
fn parse_question_mark_prefix_in_predicate_returns_error() {
let err = parse("items[?name=foo]").unwrap_err();
assert!(
err.contains("use [name=foo]"),
"expected helpful suggestion, got: {err}"
);
}
#[test]
fn parse_invalid_bracket_content_returns_error() {
let err = parse("items[abc]").unwrap_err();
assert!(
err.contains("invalid bracket content"),
"expected 'invalid bracket content', got: {err}"
);
}
#[test]
fn parse_empty_string_returns_empty_selector() {
let sel = parse("").unwrap();
assert!(sel.is_empty());
}
#[test]
fn parse_leading_dot_is_ignored() {
assert_eq!(parse(".name").unwrap(), vec![Segment::Key("name".into())]);
}
#[test]
fn parse_consecutive_dots_are_ignored() {
assert_eq!(
parse("a..b").unwrap(),
vec![Segment::Key("a".into()), Segment::Key("b".into())]
);
}
#[test]
fn parse_index_at_start() {
let sel = parse("[0].name").unwrap();
assert_eq!(sel, vec![Segment::Index(0), Segment::Key("name".into())]);
}
#[test]
fn parse_adjacent_brackets() {
let sel = parse("[0][1]").unwrap();
assert_eq!(sel, vec![Segment::Index(0), Segment::Index(1)]);
}
#[test]
fn parse_predicate_value_with_equals() {
let sel = parse("items[url=a=b]").unwrap();
assert_eq!(
sel,
vec![
Segment::Key("items".into()),
Segment::Predicate {
key: "url".into(),
value: "a=b".into(),
},
]
);
}
#[test]
fn parse_predicate_value_with_brackets() {
let sel = parse("items[pattern=[0-9]]").unwrap();
assert_eq!(
sel,
vec![
Segment::Key("items".into()),
Segment::Predicate {
key: "pattern".into(),
value: "[0-9]".into(),
},
]
);
}
#[test]
fn parse_nested_brackets_in_value() {
let sel = parse("data[regex=[a[b]c]]").unwrap();
assert_eq!(
sel,
vec![
Segment::Key("data".into()),
Segment::Predicate {
key: "regex".into(),
value: "[a[b]c]".into(),
},
]
);
}
}