src/filter/dsl/parser/grammar_test.rs

#[cfg(test)]
mod tests {
    use crate::filter::dsl::ast::{Cmp, Expr, Value};
    use crate::filter::dsl::parser::parse;

    #[test]
    fn parses_single_comparison() {
        let ast = parse("status >= 400").unwrap();
        match ast {
            Expr::Compare { field, op, value } => {
                assert_eq!(field, "status");
                assert_eq!(op, Cmp::Ge);
                assert_eq!(value, Value::Int(400));
            }
            other => panic!("unexpected AST {:?}", other),
        }
    }

    #[test]
    fn and_binds_tighter_than_or() {
        let ast = parse("a = 1 or b = 2 and c = 3").unwrap();
        match ast {
            Expr::Or(lhs, rhs) => {
                assert!(matches!(*lhs, Expr::Compare { .. }));
                assert!(matches!(*rhs, Expr::And(_, _)));
            }
            other => panic!("expected Or at top level, got {:?}", other),
        }
    }

    #[test]
    fn parentheses_flip_precedence() {
        let ast = parse("(a = 1 or b = 2) and c = 3").unwrap();
        assert!(matches!(ast, Expr::And(_, _)));
    }

    #[test]
    fn not_consumes_single_primary() {
        let ast = parse("not status = 200").unwrap();
        match ast {
            Expr::Not(inner) => assert!(matches!(*inner, Expr::Compare { .. })),
            other => panic!("expected Not, got {:?}", other),
        }
    }

    #[test]
    fn regex_literal_becomes_regex_value() {
        let ast = parse("body ~ /oops/").unwrap();
        match ast {
            Expr::Compare { op, value, .. } => {
                assert_eq!(op, Cmp::Match);
                assert_eq!(value, Value::Regex("oops".into()));
            }
            _ => panic!("expected Compare"),
        }
    }

    #[test]
    fn trailing_garbage_is_reported() {
        let err = parse("status = 200 extra").unwrap_err();
        assert!(err.to_string().contains("trailing"));
    }

    #[test]
    fn missing_value_is_reported() {
        assert!(parse("status =").is_err());
    }

    #[test]
    fn unbalanced_paren_is_reported() {
        assert!(parse("(status = 1").is_err());
    }

    #[test]
    fn empty_input_is_rejected_upstream() {
        assert!(parse("   ").is_err());
    }
}