mod accessor;
mod construction;
mod primitives;
use nom::{
branch::alt,
character::complete::{char, multispace0},
combinator::all_consuming,
multi::many0,
sequence::{delimited, preceded},
Parser,
};
use crate::error::ParseError;
use super::ast::Filter;
use super::builtins::Builtin;
pub fn parse(input: &str) -> Result<Filter, ParseError> {
match all_consuming(parse_filter).parse(input) {
Ok((_, filter)) => Ok(filter),
Err(e) => {
let (position, message) = match &e {
nom::Err::Error(e) | nom::Err::Failure(e) => {
let pos = input.len() - e.input.len();
let msg = if e.input.is_empty() {
"unexpected end of input".to_string()
} else {
format!("unexpected '{}'", e.input.chars().next().unwrap_or('?'))
};
(pos, msg)
}
nom::Err::Incomplete(_) => (input.len(), "incomplete input".to_string()),
};
Err(ParseError { message, position })
}
}
}
fn parse_filter(input: &str) -> nom::IResult<&str, Filter> {
delimited(multispace0, parse_pipe_chain, multispace0).parse(input)
}
pub(crate) fn parse_pipe_chain(input: &str) -> nom::IResult<&str, Filter> {
let (input, first) = parse_filter_term(input)?;
let (input, rest) = many0(preceded(
delimited(multispace0, char('|'), multispace0),
parse_filter_term,
))
.parse(input)?;
let filter = rest
.into_iter()
.fold(first, |acc, f| Filter::Pipe(Box::new(acc), Box::new(f)));
Ok((input, filter))
}
fn parse_filter_term(input: &str) -> nom::IResult<&str, Filter> {
alt((
construction::parse_array_construction,
construction::parse_object_construction,
accessor::parse_accessor_chain,
parse_builtin,
))
.parse(input)
}
fn parse_builtin(input: &str) -> nom::IResult<&str, Filter> {
let (rest, name) = primitives::identifier(input)?;
match Builtin::from_name(&name) {
Some(b) => Ok((rest, Filter::Builtin(b))),
None => Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Tag,
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::filter::ast::ObjectKey;
#[test]
fn test_identity() {
assert_eq!(parse("."), Ok(Filter::Identity));
}
#[test]
fn test_identity_with_whitespace() {
assert_eq!(parse(" . "), Ok(Filter::Identity));
}
#[test]
fn test_field() {
assert_eq!(parse(".foo"), Ok(Filter::Field("foo".to_string())));
assert_eq!(parse(".name"), Ok(Filter::Field("name".to_string())));
}
#[test]
fn test_field_with_whitespace() {
assert_eq!(parse(" .foo "), Ok(Filter::Field("foo".to_string())));
}
#[test]
fn test_field_chain() {
assert_eq!(
parse(".foo.bar"),
Ok(Filter::Pipe(
Box::new(Filter::Field("foo".to_string())),
Box::new(Filter::Field("bar".to_string()))
))
);
}
#[test]
fn test_field_chain_triple() {
assert_eq!(
parse(".foo.bar.baz"),
Ok(Filter::Pipe(
Box::new(Filter::Pipe(
Box::new(Filter::Field("foo".to_string())),
Box::new(Filter::Field("bar".to_string()))
)),
Box::new(Filter::Field("baz".to_string()))
))
);
}
#[test]
fn test_underscore_field() {
assert_eq!(
parse("._private"),
Ok(Filter::Field("_private".to_string()))
);
}
#[test]
fn test_field_with_numbers() {
assert_eq!(
parse(".field123"),
Ok(Filter::Field("field123".to_string()))
);
}
#[test]
fn test_unicode_field() {
assert_eq!(parse(".名前"), Ok(Filter::Field("名前".to_string())));
}
#[test]
fn test_keyword_as_field() {
assert_eq!(parse(".null"), Ok(Filter::Field("null".to_string())));
assert_eq!(parse(".true"), Ok(Filter::Field("true".to_string())));
assert_eq!(parse(".false"), Ok(Filter::Field("false".to_string())));
}
#[test]
fn test_index() {
assert_eq!(parse(".[0]"), Ok(Filter::Index(0)));
assert_eq!(parse(".[1]"), Ok(Filter::Index(1)));
assert_eq!(parse(".[42]"), Ok(Filter::Index(42)));
}
#[test]
fn test_negative_index() {
assert_eq!(parse(".[-1]"), Ok(Filter::Index(-1)));
assert_eq!(parse(".[-10]"), Ok(Filter::Index(-10)));
}
#[test]
fn test_index_with_whitespace() {
assert_eq!(parse(".[ 0 ]"), Ok(Filter::Index(0)));
assert_eq!(parse(".[ -1 ]"), Ok(Filter::Index(-1)));
}
#[test]
fn test_iterate() {
assert_eq!(parse(".[]"), Ok(Filter::Iterate));
}
#[test]
fn test_iterate_with_whitespace() {
assert_eq!(parse(".[ ]"), Ok(Filter::Iterate));
}
#[test]
fn test_quoted_field() {
assert_eq!(
parse(".[\"foo\"]"),
Ok(Filter::Field("foo".to_string()))
);
}
#[test]
fn test_quoted_field_special_chars() {
assert_eq!(
parse(".[\"foo-bar\"]"),
Ok(Filter::Field("foo-bar".to_string()))
);
assert_eq!(
parse(".[\"with space\"]"),
Ok(Filter::Field("with space".to_string()))
);
assert_eq!(
parse(".[\"a.b\"]"),
Ok(Filter::Field("a.b".to_string()))
);
}
#[test]
fn test_quoted_field_empty() {
assert_eq!(parse(".[\"\"]"), Ok(Filter::Field("".to_string())));
}
#[test]
fn test_quoted_field_unicode() {
assert_eq!(
parse(".[\"日本語\"]"),
Ok(Filter::Field("日本語".to_string()))
);
}
#[test]
fn test_quoted_field_with_whitespace() {
assert_eq!(
parse(".[ \"foo\" ]"),
Ok(Filter::Field("foo".to_string()))
);
}
#[test]
fn test_quoted_field_escapes() {
assert_eq!(
parse(".[\"a\\nb\"]"),
Ok(Filter::Field("a\nb".to_string()))
);
assert_eq!(
parse(".[\"a\\tb\"]"),
Ok(Filter::Field("a\tb".to_string()))
);
assert_eq!(
parse(".[\"a\\\"b\"]"),
Ok(Filter::Field("a\"b".to_string()))
);
assert_eq!(
parse(".[\"a\\\\b\"]"),
Ok(Filter::Field("a\\b".to_string()))
);
}
#[test]
fn test_quoted_field_unicode_escape() {
assert_eq!(
parse(".[\"\\u0041\"]"),
Ok(Filter::Field("A".to_string()))
);
}
#[test]
fn test_chain_field_index() {
assert_eq!(
parse(".foo[0]"),
Ok(Filter::Pipe(
Box::new(Filter::Field("foo".to_string())),
Box::new(Filter::Index(0))
))
);
}
#[test]
fn test_chain_index_field() {
assert_eq!(
parse(".[0].foo"),
Ok(Filter::Pipe(
Box::new(Filter::Index(0)),
Box::new(Filter::Field("foo".to_string()))
))
);
}
#[test]
fn test_chain_index_index() {
assert_eq!(
parse(".[0][1]"),
Ok(Filter::Pipe(
Box::new(Filter::Index(0)),
Box::new(Filter::Index(1))
))
);
}
#[test]
fn test_chain_iterate_index() {
assert_eq!(
parse(".[][0]"),
Ok(Filter::Pipe(
Box::new(Filter::Iterate),
Box::new(Filter::Index(0))
))
);
}
#[test]
fn test_chain_quoted_quoted() {
assert_eq!(
parse(".[\"a\"][\"b\"]"),
Ok(Filter::Pipe(
Box::new(Filter::Field("a".to_string())),
Box::new(Filter::Field("b".to_string()))
))
);
}
#[test]
fn test_chain_mixed() {
assert_eq!(
parse(".items[0].id"),
Ok(Filter::Pipe(
Box::new(Filter::Pipe(
Box::new(Filter::Field("items".to_string())),
Box::new(Filter::Index(0))
)),
Box::new(Filter::Field("id".to_string()))
))
);
}
#[test]
fn test_chain_iterate_field() {
assert_eq!(
parse(".items[].id"),
Ok(Filter::Pipe(
Box::new(Filter::Pipe(
Box::new(Filter::Field("items".to_string())),
Box::new(Filter::Iterate)
)),
Box::new(Filter::Field("id".to_string()))
))
);
}
#[test]
fn test_chain_mixed_dot_bracket() {
assert_eq!(
parse(".foo[\"bar\"].baz"),
Ok(Filter::Pipe(
Box::new(Filter::Pipe(
Box::new(Filter::Field("foo".to_string())),
Box::new(Filter::Field("bar".to_string()))
)),
Box::new(Filter::Field("baz".to_string()))
))
);
}
#[test]
fn test_trailing_dot_error() {
assert!(parse(".foo.").is_err());
}
#[test]
fn test_digit_start_error() {
assert!(parse(".123").is_err());
}
#[test]
fn test_empty() {
assert!(parse("").is_err());
}
#[test]
fn test_invalid() {
assert!(parse("..").is_err());
assert!(parse("foo").is_err());
}
#[test]
fn test_unclosed_bracket() {
assert!(parse(".[").is_err());
assert!(parse(".[0").is_err());
}
#[test]
fn test_unclosed_string() {
assert!(parse(".[\"unclosed").is_err());
}
#[test]
fn test_single_quotes_error() {
assert!(parse(".['field']").is_err());
}
#[test]
fn test_float_error() {
assert!(parse(".[0.5]").is_err());
}
#[test]
fn test_integer_overflow() {
assert!(parse(".[99999999999999999999]").is_err());
}
#[test]
fn test_pipe_simple() {
assert_eq!(
parse(".foo | .bar"),
Ok(Filter::Pipe(
Box::new(Filter::Field("foo".to_string())),
Box::new(Filter::Field("bar".to_string()))
))
);
}
#[test]
fn test_pipe_with_whitespace() {
assert_eq!(
parse(".foo|.bar"),
Ok(Filter::Pipe(
Box::new(Filter::Field("foo".to_string())),
Box::new(Filter::Field("bar".to_string()))
))
);
assert_eq!(
parse(".foo | .bar"),
Ok(Filter::Pipe(
Box::new(Filter::Field("foo".to_string())),
Box::new(Filter::Field("bar".to_string()))
))
);
}
#[test]
fn test_pipe_triple() {
assert_eq!(
parse(".a | .b | .c"),
Ok(Filter::Pipe(
Box::new(Filter::Pipe(
Box::new(Filter::Field("a".to_string())),
Box::new(Filter::Field("b".to_string()))
)),
Box::new(Filter::Field("c".to_string()))
))
);
}
#[test]
fn test_pipe_with_iterate() {
assert_eq!(
parse(".users | .[]"),
Ok(Filter::Pipe(
Box::new(Filter::Field("users".to_string())),
Box::new(Filter::Iterate)
))
);
}
#[test]
fn test_pipe_with_index() {
assert_eq!(
parse(".items | .[0]"),
Ok(Filter::Pipe(
Box::new(Filter::Field("items".to_string())),
Box::new(Filter::Index(0))
))
);
}
#[test]
fn test_pipe_mixed_implicit_explicit() {
assert_eq!(
parse(".users[].name | .foo"),
Ok(Filter::Pipe(
Box::new(Filter::Pipe(
Box::new(Filter::Pipe(
Box::new(Filter::Field("users".to_string())),
Box::new(Filter::Iterate)
)),
Box::new(Filter::Field("name".to_string()))
)),
Box::new(Filter::Field("foo".to_string()))
))
);
}
#[test]
fn test_pipe_identity() {
assert_eq!(
parse(". | .foo"),
Ok(Filter::Pipe(
Box::new(Filter::Identity),
Box::new(Filter::Field("foo".to_string()))
))
);
}
#[test]
fn test_pipe_trailing_error() {
assert!(parse(".foo |").is_err());
}
#[test]
fn test_pipe_leading_error() {
assert!(parse("| .foo").is_err());
}
#[test]
fn test_pipe_double_error() {
assert!(parse(".foo || .bar").is_err());
}
#[test]
fn test_pipe_empty_segment_error() {
assert!(parse(".foo | | .bar").is_err());
}
#[test]
fn test_builtin_standalone() {
assert_eq!(
parse("length"),
Ok(Filter::Builtin(Builtin::Length))
);
assert_eq!(
parse("keys"),
Ok(Filter::Builtin(Builtin::Keys))
);
assert_eq!(
parse("type"),
Ok(Filter::Builtin(Builtin::Type))
);
}
#[test]
fn test_builtin_with_whitespace() {
assert_eq!(
parse(" length "),
Ok(Filter::Builtin(Builtin::Length))
);
}
#[test]
fn test_builtin_after_pipe() {
assert_eq!(
parse(".foo | length"),
Ok(Filter::Pipe(
Box::new(Filter::Field("foo".to_string())),
Box::new(Filter::Builtin(Builtin::Length))
))
);
}
#[test]
fn test_builtin_before_pipe() {
assert_eq!(
parse("length | .foo"),
Ok(Filter::Pipe(
Box::new(Filter::Builtin(Builtin::Length)),
Box::new(Filter::Field("foo".to_string()))
))
);
}
#[test]
fn test_builtin_chain() {
assert_eq!(
parse(".items | length"),
Ok(Filter::Pipe(
Box::new(Filter::Field("items".to_string())),
Box::new(Filter::Builtin(Builtin::Length))
))
);
}
#[test]
fn test_iterate_then_builtin() {
assert_eq!(
parse(".[] | length"),
Ok(Filter::Pipe(
Box::new(Filter::Iterate),
Box::new(Filter::Builtin(Builtin::Length))
))
);
}
#[test]
fn test_builtin_then_iterate() {
assert_eq!(
parse("keys | .[]"),
Ok(Filter::Pipe(
Box::new(Filter::Builtin(Builtin::Keys)),
Box::new(Filter::Iterate)
))
);
}
#[test]
fn test_multiple_builtins() {
assert_eq!(
parse("sort | reverse"),
Ok(Filter::Pipe(
Box::new(Filter::Builtin(Builtin::Sort)),
Box::new(Filter::Builtin(Builtin::Reverse))
))
);
}
#[test]
fn test_all_builtins_parse() {
assert_eq!(parse("length"), Ok(Filter::Builtin(Builtin::Length)));
assert_eq!(parse("keys"), Ok(Filter::Builtin(Builtin::Keys)));
assert_eq!(parse("values"), Ok(Filter::Builtin(Builtin::Values)));
assert_eq!(parse("type"), Ok(Filter::Builtin(Builtin::Type)));
assert_eq!(parse("first"), Ok(Filter::Builtin(Builtin::First)));
assert_eq!(parse("last"), Ok(Filter::Builtin(Builtin::Last)));
assert_eq!(parse("reverse"), Ok(Filter::Builtin(Builtin::Reverse)));
assert_eq!(parse("sort"), Ok(Filter::Builtin(Builtin::Sort)));
assert_eq!(parse("min"), Ok(Filter::Builtin(Builtin::Min)));
assert_eq!(parse("max"), Ok(Filter::Builtin(Builtin::Max)));
assert_eq!(parse("unique"), Ok(Filter::Builtin(Builtin::Unique)));
assert_eq!(parse("flatten"), Ok(Filter::Builtin(Builtin::Flatten)));
assert_eq!(parse("add"), Ok(Filter::Builtin(Builtin::Add)));
assert_eq!(parse("empty"), Ok(Filter::Builtin(Builtin::Empty)));
assert_eq!(parse("not"), Ok(Filter::Builtin(Builtin::Not)));
}
#[test]
fn test_unknown_identifier_error() {
assert!(parse("notabuiltin").is_err());
}
#[test]
fn test_builtin_as_field_name() {
assert_eq!(
parse(".length"),
Ok(Filter::Field("length".to_string()))
);
}
#[test]
fn test_array_construction_simple() {
assert_eq!(
parse("[.foo, .bar]"),
Ok(Filter::Array(vec![
Filter::Field("foo".to_string()),
Filter::Field("bar".to_string()),
]))
);
}
#[test]
fn test_array_construction_empty() {
assert_eq!(parse("[]"), Ok(Filter::Array(vec![])));
}
#[test]
fn test_array_construction_single() {
assert_eq!(
parse("[.foo]"),
Ok(Filter::Array(vec![Filter::Field("foo".to_string())]))
);
}
#[test]
fn test_array_construction_with_pipe() {
assert_eq!(
parse("[.foo | length]"),
Ok(Filter::Array(vec![Filter::Pipe(
Box::new(Filter::Field("foo".to_string())),
Box::new(Filter::Builtin(Builtin::Length))
)]))
);
}
#[test]
fn test_array_construction_with_iterate() {
assert_eq!(
parse("[.[]]"),
Ok(Filter::Array(vec![Filter::Iterate]))
);
}
#[test]
fn test_array_construction_nested() {
assert_eq!(
parse("[[.x], [.y]]"),
Ok(Filter::Array(vec![
Filter::Array(vec![Filter::Field("x".to_string())]),
Filter::Array(vec![Filter::Field("y".to_string())]),
]))
);
}
#[test]
fn test_array_construction_with_whitespace() {
assert_eq!(
parse("[ .foo , .bar ]"),
Ok(Filter::Array(vec![
Filter::Field("foo".to_string()),
Filter::Field("bar".to_string()),
]))
);
}
#[test]
fn test_object_construction_simple() {
assert_eq!(
parse("{name: .foo}"),
Ok(Filter::Object(vec![(
ObjectKey::Static("name".to_string()),
Filter::Field("foo".to_string())
)]))
);
}
#[test]
fn test_object_construction_empty() {
assert_eq!(parse("{}"), Ok(Filter::Object(vec![])));
}
#[test]
fn test_object_construction_multiple() {
assert_eq!(
parse("{name: .foo, age: .bar}"),
Ok(Filter::Object(vec![
(
ObjectKey::Static("name".to_string()),
Filter::Field("foo".to_string())
),
(
ObjectKey::Static("age".to_string()),
Filter::Field("bar".to_string())
),
]))
);
}
#[test]
fn test_object_construction_quoted_key() {
assert_eq!(
parse("{\"my-key\": .foo}"),
Ok(Filter::Object(vec![(
ObjectKey::Static("my-key".to_string()),
Filter::Field("foo".to_string())
)]))
);
}
#[test]
fn test_object_construction_dynamic_key() {
assert_eq!(
parse("{(.key): .value}"),
Ok(Filter::Object(vec![(
ObjectKey::Dynamic(Box::new(Filter::Field("key".to_string()))),
Filter::Field("value".to_string())
)]))
);
}
#[test]
fn test_object_construction_with_pipe() {
assert_eq!(
parse("{count: .items | length}"),
Ok(Filter::Object(vec![(
ObjectKey::Static("count".to_string()),
Filter::Pipe(
Box::new(Filter::Field("items".to_string())),
Box::new(Filter::Builtin(Builtin::Length))
)
)]))
);
}
#[test]
fn test_object_construction_with_whitespace() {
assert_eq!(
parse("{ name : .foo , age : .bar }"),
Ok(Filter::Object(vec![
(
ObjectKey::Static("name".to_string()),
Filter::Field("foo".to_string())
),
(
ObjectKey::Static("age".to_string()),
Filter::Field("bar".to_string())
),
]))
);
}
#[test]
fn test_nested_object_in_array() {
assert_eq!(
parse("[{a: .x}]"),
Ok(Filter::Array(vec![Filter::Object(vec![(
ObjectKey::Static("a".to_string()),
Filter::Field("x".to_string())
)])]))
);
}
#[test]
fn test_array_in_object() {
assert_eq!(
parse("{point: [.x, .y]}"),
Ok(Filter::Object(vec![(
ObjectKey::Static("point".to_string()),
Filter::Array(vec![
Filter::Field("x".to_string()),
Filter::Field("y".to_string()),
])
)]))
);
}
#[test]
fn test_optional_field() {
assert_eq!(
parse(".foo?"),
Ok(Filter::Optional(Box::new(Filter::Field("foo".to_string()))))
);
}
#[test]
fn test_optional_index() {
assert_eq!(
parse(".[0]?"),
Ok(Filter::Optional(Box::new(Filter::Index(0))))
);
}
#[test]
fn test_optional_iterate() {
assert_eq!(
parse(".[]?"),
Ok(Filter::Optional(Box::new(Filter::Iterate)))
);
}
#[test]
fn test_optional_chained() {
assert_eq!(
parse(".foo?.bar"),
Ok(Filter::Pipe(
Box::new(Filter::Optional(Box::new(Filter::Field("foo".to_string())))),
Box::new(Filter::Field("bar".to_string()))
))
);
}
#[test]
fn test_optional_both() {
assert_eq!(
parse(".foo?.bar?"),
Ok(Filter::Pipe(
Box::new(Filter::Optional(Box::new(Filter::Field("foo".to_string())))),
Box::new(Filter::Optional(Box::new(Filter::Field("bar".to_string()))))
))
);
}
#[test]
fn test_optional_quoted_field() {
assert_eq!(
parse(".[\"foo\"]?"),
Ok(Filter::Optional(Box::new(Filter::Field("foo".to_string()))))
);
}
#[test]
fn test_slice_both() {
assert_eq!(parse(".[2:5]"), Ok(Filter::Slice(Some(2), Some(5))));
}
#[test]
fn test_slice_start_only() {
assert_eq!(parse(".[2:]"), Ok(Filter::Slice(Some(2), None)));
}
#[test]
fn test_slice_end_only() {
assert_eq!(parse(".[:5]"), Ok(Filter::Slice(None, Some(5))));
}
#[test]
fn test_slice_neither() {
assert_eq!(parse(".[:]"), Ok(Filter::Slice(None, None)));
}
#[test]
fn test_slice_negative_start() {
assert_eq!(parse(".[-2:]"), Ok(Filter::Slice(Some(-2), None)));
}
#[test]
fn test_slice_negative_end() {
assert_eq!(parse(".[:-1]"), Ok(Filter::Slice(None, Some(-1))));
}
#[test]
fn test_slice_both_negative() {
assert_eq!(parse(".[-3:-1]"), Ok(Filter::Slice(Some(-3), Some(-1))));
}
#[test]
fn test_slice_with_whitespace() {
assert_eq!(parse(".[ 2 : 5 ]"), Ok(Filter::Slice(Some(2), Some(5))));
}
#[test]
fn test_slice_chained() {
assert_eq!(
parse(".[2:5][0]"),
Ok(Filter::Pipe(
Box::new(Filter::Slice(Some(2), Some(5))),
Box::new(Filter::Index(0))
))
);
}
}