OData v4 Params Parsing Library
This library provides a parser for OData v4 $filter expressions.
It converts these expressions into an Abstract Syntax Tree (AST),
allowing for further processing and evaluation. The parser supports
a wide range of logical operators, comparison operators, function calls,
and nested expressions, making it highly versatile for complex querying needs.
Note that this is a work in progress. A full list of specification components that are supported and not supported will be added in the next release.
Features
- Logical Operators:
and,or,not - Comparison Operators:
eq,ne,gt,lt,ge,le - Function Calls: Ex:
startswith,endswith,contains,concat - Grouping: Nested expressions with parentheses
- Data Types: String, Number, Boolean, Date, Time, DateTime with Time Zone
Not Yet Implemented
- Lambda Functions:
any,all - Parameter Aliases:
@something - The
hasOperator
Data Types
The library supports the following data types in expressions:
- String: Enclosed in single quotes
'example' - Number: Integer and decimal numbers
123,45.67 - UUID: UUIDs
da820b39-5ad2-4441-b664-c902dbd377d8 - Boolean:
true,false - Time: ISO 8601 format
HH:MM:SS - Date: ISO 8601 format
YYYY-MM-DD - DateTime: ISO 8601 format with time zone
YYYY-MM-DDTHH:MM:SSZ
Testing
The library includes a set of tests mostly generated by AI to catch regressions. The test files contain numerous examples to see how the library can be used and what outputs to expect.
You can run the tests using the following command:
Installation
To add this library to your project, add the following to your Cargo.toml:
[]
= "0.2.0"
Or run cargo add odata-params.
Example
Here is an example of how to parse a simple filter expression:
use ;
Supported Expressions
Logical Operators
use parse_str;
let filter = "name eq 'John' or isActive eq true";
let result = parse_str.expect;
// Expected Expr structure:
// Expr::Or(
// Box::new(Expr::Compare(
// Box::new(Expr::Identifier("name".to_owned())),
// CompareOperator::Equal,
// Box::new(Expr::Value(Value::String("John".to_owned()))),
// )),
// Box::new(Expr::Compare(
// Box::new(Expr::Identifier("isActive".to_owned())),
// CompareOperator::Equal,
// Box::new(Expr::Value(Value::Bool(true))),
// )),
// )
Comparison Operators
use parse_str;
let filter = "price lt 99.99";
let result = parse_str.expect;
// Expected Expr structure:
// Expr::Compare(
// Box::new(Expr::Identifier("price".to_owned())),
// CompareOperator::LessThan,
// Box::new(Expr::Value(Value::Number(BigDecimal::from_str("99.99").unwrap()))),
// )
Function Calls
use parse_str;
let filter = "endswith(name, 'Smith')";
let result = parse_str.expect;
// Expected Expr structure:
// Expr::Function(
// "endswith".to_owned(),
// vec![
// Expr::Identifier("name".to_owned()),
// Expr::Value(Value::String("Smith".to_owned()))
// ]
// )
Advanced Usage
Nested Grouping
use parse_str;
let filter = "((name eq 'John' and isActive eq true) or (age gt 30 and age lt 50))";
let result = parse_str.expect;
// Expected Expr structure:
// Expr::Or(
// Box::new(Expr::And(
// Box::new(Expr::Compare(
// Box::new(Expr::Identifier("name".to_owned())),
// CompareOperator::Equal,
// Box::new(Expr::Value(Value::String("John".to_owned()))),
// )),
// Box::new(Expr::Compare(
// Box::new(Expr::Identifier("isActive".to_owned())),
// CompareOperator::Equal,
// Box::new(Expr::Value(Value::Bool(true))),
// )),
// )),
// Box::new(Expr::And(
// Box::new(Expr::Compare(
// Box::new(Expr::Identifier("age".to_owned())),
// CompareOperator::GreaterThan,
// Box::new(Expr::Value(Value::Number(BigDecimal::from_str("30").unwrap()))),
// )),
// Box::new(Expr::Compare(
// Box::new(Expr::Identifier("age".to_owned())),
// CompareOperator::LessThan,
// Box::new(Expr::Value(Value::Number(BigDecimal::from_str("50").unwrap()))),
// )),
// )),
// )
Functions with Comparisons
use parse_str;
let filter = "concat(concat(city, ', '), country) eq 'Berlin, Germany'";
let result = parse_str.expect;
// Expected Expr structure:
// Expr::Compare(
// Box::new(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())
// ]
// )),
// CompareOperator::Equal,
// Box::new(Expr::Value(Value::String("Berlin, Germany".to_owned())))
// )