use odata_params::bigdecimal::BigDecimal; use odata_params::filters::CompareOperator::*; use odata_params::filters::{parse_str, Expr, Value}; use std::str::FromStr; #[test] fn or_grouping() { let filter = "name eq 'John' or isActive eq true"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Or( Expr::Compare( Expr::Identifier("name".to_owned()).into(), Equal, Expr::Value(Value::String("John".to_owned())).into() ) .into(), Expr::Compare( Expr::Identifier("isActive".to_owned()).into(), Equal, Expr::Value(Value::Bool(true)).into() ) .into() ) ); } #[test] fn and_grouping() { let filter = "name eq 'John' and isActive eq true"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::And( Expr::Compare( Expr::Identifier("name".to_owned()).into(), Equal, Expr::Value(Value::String("John".to_owned())).into() ) .into(), Expr::Compare( Expr::Identifier("isActive".to_owned()).into(), Equal, Expr::Value(Value::Bool(true)).into() ) .into() ) ); } #[test] fn not_grouping() { let filter = "not (name eq 'John')"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Not( Expr::Compare( Expr::Identifier("name".to_owned()).into(), Equal, Expr::Value(Value::String("John".to_owned())).into() ) .into() ) ); } #[test] fn complex_and_or_grouping() { let filter = "(name eq 'John' and isActive eq true) or age gt 30"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Or( Expr::And( Expr::Compare( Expr::Identifier("name".to_owned()).into(), Equal, Expr::Value(Value::String("John".to_owned())).into() ) .into(), Expr::Compare( Expr::Identifier("isActive".to_owned()).into(), Equal, Expr::Value(Value::Bool(true)).into() ) .into() ) .into(), Expr::Compare( Expr::Identifier("age".to_owned()).into(), GreaterThan, Expr::Value(Value::Number(BigDecimal::from_str("30").unwrap())).into() ) .into() ) ); } #[test] fn nested_grouping() { let filter = "((name eq 'John' and isActive eq true) or (age gt 30 and age lt 50))"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Or( Expr::And( Expr::Compare( Expr::Identifier("name".to_owned()).into(), Equal, Expr::Value(Value::String("John".to_owned())).into() ) .into(), Expr::Compare( Expr::Identifier("isActive".to_owned()).into(), Equal, Expr::Value(Value::Bool(true)).into() ) .into() ) .into(), Expr::And( Expr::Compare( Expr::Identifier("age".to_owned()).into(), GreaterThan, Expr::Value(Value::Number(BigDecimal::from_str("30").unwrap())).into() ) .into(), Expr::Compare( Expr::Identifier("age".to_owned()).into(), LessThan, Expr::Value(Value::Number(BigDecimal::from_str("50").unwrap())).into() ) .into() ) .into() ) ); } #[test] fn function_call_endswith() { let filter = "endswith(name, 'Smith')"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Function( "endswith".to_owned(), vec![ Expr::Identifier("name".to_owned()), Expr::Value(Value::String("Smith".to_owned())) ] ) ); } #[test] fn function_call_complex() { let filter = "concat(concat(city, ', '), country) eq 'Berlin, Germany'"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Compare( Expr::Function( "concat".to_owned(), vec![ Expr::Function( "concat".to_owned(), vec![ Expr::Identifier("city".to_owned()), Expr::Value(Value::String(", ".to_owned())) ] ), Expr::Identifier("country".to_owned()) ] ) .into(), Equal, Expr::Value(Value::String("Berlin, Germany".to_owned())).into() ) ); } #[test] fn in_operator() { let filter = "name in ('John', 'Jane', 'Doe')"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::In( Expr::Identifier("name".to_owned()).into(), vec![ Expr::Value(Value::String("John".to_owned())), Expr::Value(Value::String("Jane".to_owned())), Expr::Value(Value::String("Doe".to_owned())) ] ) ); } #[test] fn nested_not() { let filter = "not (not (isActive eq false))"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Not( Expr::Not( Expr::Compare( Expr::Identifier("isActive".to_owned()).into(), Equal, Expr::Value(Value::Bool(false)).into() ) .into() ) .into() ) ); } #[test] fn complex_nested() { let filter = "((name eq 'John' and isActive eq true) or (age gt 30 and age lt 50)) and (city eq 'Berlin' or city eq 'Paris')"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::And( Expr::Or( Expr::And( Expr::Compare( Expr::Identifier("name".to_owned()).into(), Equal, Expr::Value(Value::String("John".to_owned())).into() ) .into(), Expr::Compare( Expr::Identifier("isActive".to_owned()).into(), Equal, Expr::Value(Value::Bool(true)).into() ) .into() ) .into(), Expr::And( Expr::Compare( Expr::Identifier("age".to_owned()).into(), GreaterThan, Expr::Value(Value::Number(BigDecimal::from_str("30").unwrap())).into() ) .into(), Expr::Compare( Expr::Identifier("age".to_owned()).into(), LessThan, Expr::Value(Value::Number(BigDecimal::from_str("50").unwrap())).into() ) .into() ) .into() ) .into(), Expr::Or( Expr::Compare( Expr::Identifier("city".to_owned()).into(), Equal, Expr::Value(Value::String("Berlin".to_owned())).into() ) .into(), Expr::Compare( Expr::Identifier("city".to_owned()).into(), Equal, Expr::Value(Value::String("Paris".to_owned())).into() ) .into() ) .into() ) ); } #[test] fn function_and_comparison() { let filter = "substring(name, 1, 3) eq 'Joh'"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Compare( Expr::Function( "substring".to_owned(), vec![ Expr::Identifier("name".to_owned()), Expr::Value(Value::Number(BigDecimal::from_str("1").unwrap())), Expr::Value(Value::Number(BigDecimal::from_str("3").unwrap())) ] ) .into(), Equal, Expr::Value(Value::String("Joh".to_owned())).into() ) ); } #[test] fn nested_function_calls() { let filter = "concat(substring(name, 1, 3), ' Doe') eq 'Joh Doe'"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Compare( Expr::Function( "concat".to_owned(), vec![ Expr::Function( "substring".to_owned(), vec![ Expr::Identifier("name".to_owned()), Expr::Value(Value::Number(BigDecimal::from_str("1").unwrap())), Expr::Value(Value::Number(BigDecimal::from_str("3").unwrap())) ] ), Expr::Value(Value::String(" Doe".to_owned())) ] ) .into(), Equal, Expr::Value(Value::String("Joh Doe".to_owned())).into() ) ); } #[test] fn not_and_function() { let filter = "not endswith(name, 'Smith')"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Not( Expr::Function( "endswith".to_owned(), vec![ Expr::Identifier("name".to_owned()), Expr::Value(Value::String("Smith".to_owned())) ] ) .into() ) ); } #[test] fn mixed_operators() { let filter = "price gt 50.0 and (name eq 'John' or endswith(name, 'Doe'))"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::And( Expr::Compare( Expr::Identifier("price".to_owned()).into(), GreaterThan, Expr::Value(Value::Number(BigDecimal::from_str("50.0").unwrap())).into() ) .into(), Expr::Or( Expr::Compare( Expr::Identifier("name".to_owned()).into(), Equal, Expr::Value(Value::String("John".to_owned())).into() ) .into(), Expr::Function( "endswith".to_owned(), vec![ Expr::Identifier("name".to_owned()), Expr::Value(Value::String("Doe".to_owned())) ] ) .into() ) .into() ) ); } #[test] fn not_in_operator() { let filter = "not name in ('John', 'Jane', 'Doe')"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Not( Expr::In( Expr::Identifier("name".to_owned()).into(), vec![ Expr::Value(Value::String("John".to_owned())), Expr::Value(Value::String("Jane".to_owned())), Expr::Value(Value::String("Doe".to_owned())) ] ) .into() ) ); } #[test] fn nested_comparisons() { let filter = "((price gt 50.0 and price lt 100.0) or (discount eq 10.0 and isAvailable eq true))"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Or( Expr::And( Expr::Compare( Expr::Identifier("price".to_owned()).into(), GreaterThan, Expr::Value(Value::Number(BigDecimal::from_str("50.0").unwrap())).into() ) .into(), Expr::Compare( Expr::Identifier("price".to_owned()).into(), LessThan, Expr::Value(Value::Number(BigDecimal::from_str("100.0").unwrap())).into() ) .into() ) .into(), Expr::And( Expr::Compare( Expr::Identifier("discount".to_owned()).into(), Equal, Expr::Value(Value::Number(BigDecimal::from_str("10.0").unwrap())).into() ) .into(), Expr::Compare( Expr::Identifier("isAvailable".to_owned()).into(), Equal, Expr::Value(Value::Bool(true)).into() ) .into() ) .into() ) ); } #[test] fn multiple_functions() { let filter = "startswith(name, 'J') and length(name) gt 3"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::And( Expr::Function( "startswith".to_owned(), vec![ Expr::Identifier("name".to_owned()), Expr::Value(Value::String("J".to_owned())) ] ) .into(), Expr::Compare( Expr::Function( "length".to_owned(), vec![Expr::Identifier("name".to_owned())] ) .into(), GreaterThan, Expr::Value(Value::Number(BigDecimal::from_str("3").unwrap())).into() ) .into() ) ); } #[test] fn boolean_function() { let filter = "isActive eq true and not contains(name, 'Admin')"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::And( Expr::Compare( Expr::Identifier("isActive".to_owned()).into(), Equal, Expr::Value(Value::Bool(true)).into() ) .into(), Expr::Not( Expr::Function( "contains".to_owned(), vec![ Expr::Identifier("name".to_owned()), Expr::Value(Value::String("Admin".to_owned())) ] ) .into() ) .into() ) ); } #[test] fn nested_and_or_not() { let filter = "not ((price gt 50.0 or price lt 30.0) and not (discount eq 5.0 or discount eq 10.0))"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::Not( Expr::And( Expr::Or( Expr::Compare( Expr::Identifier("price".to_owned()).into(), GreaterThan, Expr::Value(Value::Number(BigDecimal::from_str("50.0").unwrap())).into() ) .into(), Expr::Compare( Expr::Identifier("price".to_owned()).into(), LessThan, Expr::Value(Value::Number(BigDecimal::from_str("30.0").unwrap())).into() ) .into() ) .into(), Expr::Not( Expr::Or( Expr::Compare( Expr::Identifier("discount".to_owned()).into(), Equal, Expr::Value(Value::Number(BigDecimal::from_str("5.0").unwrap())).into() ) .into(), Expr::Compare( Expr::Identifier("discount".to_owned()).into(), Equal, Expr::Value(Value::Number(BigDecimal::from_str("10.0").unwrap())) .into() ) .into() ) .into() ) .into() ) .into() ) ); } #[test] fn multiple_nested_functions() { let filter = "concat(concat(city, ', '), country) eq 'Berlin, Germany' and contains(description, 'sample')"; let result = parse_str(filter).expect("valid filter tree"); assert_eq!( result, Expr::And( Expr::Compare( Expr::Function( "concat".to_owned(), vec![ Expr::Function( "concat".to_owned(), vec![ Expr::Identifier("city".to_owned()), Expr::Value(Value::String(", ".to_owned())) ] ), Expr::Identifier("country".to_owned()) ] ) .into(), Equal, Expr::Value(Value::String("Berlin, Germany".to_owned())).into() ) .into(), Expr::Function( "contains".to_owned(), vec![ Expr::Identifier("description".to_owned()), Expr::Value(Value::String("sample".to_owned())) ] ) .into() ) ); }