use winnow::{
ascii::{alpha1, digit1},
combinator::{alt, delimited, repeat},
token::take_while,
PResult, Parser,
};
#[derive(Debug, Clone, PartialEq)]
pub enum PathSegment {
Field(String),
Index(usize),
Wildcard,
RecursiveWildcard,
TypeFilter(String),
}
pub type ParseResult<T> = Result<T, ParseError>;
#[derive(Debug, Clone)]
pub struct ParseError {
pub message: String,
pub position: usize,
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Parse error at position {}: {}",
self.position, self.message
)
}
}
impl std::error::Error for ParseError {}
fn parse_identifier(input: &mut &str) -> PResult<String> {
(
alpha1,
take_while(0.., |c: char| c.is_alphanumeric() || c == '_'),
)
.recognize()
.map(|s: &str| s.to_string())
.parse_next(input)
}
fn parse_number(input: &mut &str) -> PResult<usize> {
digit1.try_map(|s: &str| s.parse()).parse_next(input)
}
fn skip_whitespace(input: &mut &str) -> PResult<()> {
take_while(0.., |c: char| c == ' ' || c == '\t')
.void()
.parse_next(input)
}
fn parse_field(input: &mut &str) -> PResult<PathSegment> {
alt((
('.', parse_identifier).map(|(_, name)| PathSegment::Field(name)),
parse_identifier.map(PathSegment::Field),
))
.parse_next(input)
}
fn parse_index(input: &mut &str) -> PResult<PathSegment> {
delimited(
'[',
alt((
'*'.value(PathSegment::Wildcard),
parse_number.map(PathSegment::Index),
winnow::combinator::empty.value(PathSegment::Wildcard),
)),
']',
)
.parse_next(input)
}
fn parse_wildcard(input: &mut &str) -> PResult<PathSegment> {
if input.starts_with("**") {
return Err(winnow::error::ErrMode::Backtrack(
winnow::error::ParserError::from_error_kind(
input,
winnow::error::ErrorKind::Verify,
),
));
}
'*'.value(PathSegment::Wildcard).parse_next(input)
}
fn parse_recursive_wildcard(input: &mut &str) -> PResult<PathSegment> {
"**".value(PathSegment::RecursiveWildcard).parse_next(input)
}
fn parse_type_filter(input: &mut &str) -> PResult<PathSegment> {
(skip_whitespace, '|', skip_whitespace, parse_identifier)
.map(|(_, _, _, type_name)| PathSegment::TypeFilter(type_name))
.parse_next(input)
}
fn parse_segment(input: &mut &str) -> PResult<PathSegment> {
alt((
parse_recursive_wildcard, parse_type_filter, parse_field,
parse_index,
parse_wildcard,
))
.parse_next(input)
}
fn parse_path_internal(input: &mut &str) -> PResult<Vec<PathSegment>> {
skip_whitespace.parse_next(input)?;
let segments = repeat(0.., parse_segment).parse_next(input)?;
skip_whitespace.parse_next(input)?;
Ok(segments)
}
pub fn parse_path(input: &str) -> ParseResult<Vec<PathSegment>> {
let mut input_ref = input;
match parse_path_internal.parse_next(&mut input_ref) {
Ok(segments) => {
if input_ref.is_empty() {
Ok(segments)
} else {
Err(ParseError {
message: format!("Unexpected characters: '{input_ref}'"),
position: input.len() - input_ref.len(),
})
}
}
Err(e) => Err(ParseError {
message: format!("Failed to parse path: {e:?}"),
position: input.len() - input_ref.len(),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_field() {
let result = parse_path(".name").unwrap();
assert_eq!(result, vec![PathSegment::Field("name".to_string())]);
}
#[test]
fn test_parse_index() {
let result = parse_path("[0]").unwrap();
assert_eq!(result, vec![PathSegment::Index(0)]);
}
#[test]
fn test_parse_wildcard() {
let result = parse_path("*").unwrap();
assert_eq!(result, vec![PathSegment::Wildcard]);
}
#[test]
fn test_parse_recursive_wildcard() {
let result = parse_path("**").unwrap();
assert_eq!(result, vec![PathSegment::RecursiveWildcard]);
}
#[test]
fn test_parse_complex_path() {
let result = parse_path(".users[0].name").unwrap();
assert_eq!(
result,
vec![
PathSegment::Field("users".to_string()),
PathSegment::Index(0),
PathSegment::Field("name".to_string()),
]
);
}
#[test]
fn test_parse_with_wildcards() {
let result = parse_path(".users[*].name").unwrap();
assert_eq!(
result,
vec![
PathSegment::Field("users".to_string()),
PathSegment::Wildcard,
PathSegment::Field("name".to_string()),
]
);
}
#[test]
fn test_parse_type_filter() {
let result = parse_path(".users[*] | string").unwrap();
assert_eq!(
result,
vec![
PathSegment::Field("users".to_string()),
PathSegment::Wildcard,
PathSegment::TypeFilter("string".to_string()),
]
);
}
}