use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Segment {
Key(String),
Index(usize),
}
pub fn parse_path(s: &str) -> Result<Vec<Segment>, String> {
if s.is_empty() {
return Err("empty path".to_string());
}
let bytes = s.as_bytes();
let mut segments = Vec::new();
let mut i = 0;
while i < bytes.len() {
match bytes[i] {
b'.' => {
i += 1;
let start = i;
if i >= bytes.len() {
return Err(format!("expected identifier at position {i}"));
}
if !is_ident_start(bytes[i]) {
return Err(format!(
"expected identifier start at position {i}, found {:?}",
bytes[i] as char
));
}
i += 1;
while i < bytes.len() && is_ident_continue(bytes[i]) {
i += 1;
}
let ident = &s[start..i];
segments.push(Segment::Key(ident.to_string()));
}
b'[' => {
i += 1;
let start = i;
while i < bytes.len() && bytes[i].is_ascii_digit() {
i += 1;
}
if start == i {
return Err(format!("expected digits at position {start}"));
}
let digits = &s[start..i];
if i >= bytes.len() || bytes[i] != b']' {
return Err(format!("expected ']' at position {i}"));
}
let index: usize = digits
.parse()
.map_err(|e| format!("invalid index {digits:?}: {e}"))?;
i += 1; segments.push(Segment::Index(index));
}
other => {
return Err(format!(
"expected '.' or '[' at position {i}, found {:?}",
other as char
));
}
}
}
if segments.is_empty() {
return Err("empty path".to_string());
}
Ok(segments)
}
pub fn extract<'a>(value: &'a Value, path: &[Segment]) -> Option<&'a Value> {
let mut cur = value;
for seg in path {
match seg {
Segment::Key(k) => cur = cur.as_object()?.get(k)?,
Segment::Index(i) => cur = cur.as_array()?.get(*i)?,
}
}
Some(cur)
}
fn is_ident_start(b: u8) -> bool {
b.is_ascii_alphabetic() || b == b'_'
}
fn is_ident_continue(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_'
}