Skip to content

Commit 3cb0849

Browse files
authored
feat: Enhance filter expression parsing and evaluation (#222)
1 parent 0e04cfb commit 3cb0849

File tree

18 files changed

+7911
-1763
lines changed

18 files changed

+7911
-1763
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ regex = "1.11.0"
4444
reqwest = { version = "=0.12.15", features = ["json"] }
4545
reqwest-middleware = "0.4.1"
4646
reqwest-retry = "0.7.0"
47+
rust_decimal = "1.37.1"
4748
serde = { version = "1.0", features = ["derive"] }
4849
serde_json = "1.0"
4950
sha2 = "0.10.0"
@@ -62,6 +63,7 @@ tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
6263
url = "2.5"
6364
urlencoding = "2.1.3"
6465
uuid = "1.15.0"
66+
winnow = "0.7.9"
6567
zeroize = { version = "1.8.1", features = ["derive"] }
6668

6769
[dev-dependencies]

docs/modules/ROOT/pages/index.adoc

Lines changed: 290 additions & 58 deletions
Large diffs are not rendered by default.
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
//! This module defines the abstract syntax tree (AST) for the filter expressions.
2+
//! Parsing module will convert the input string into this AST structure.
3+
//! This AST is then traversed and interpreted by the evaluation module (via helpers::evaluate) to determine the result of the filter expression.
4+
//!
5+
//! The AST is designed to be a direct representation of the parsed filter expression, capturing it's structure, operators and literal values.
6+
//! Lifetime annotations (`'a`) are used to ensure that the references to string literals are valid for the duration of the expression evaluation.
7+
8+
/// Represents the possible literal values that can be used in filter expressions.
9+
/// The `LiteralValue` enum captures the different constant values that are used on the right side of a condition (RHS).
10+
#[derive(Debug, Clone, PartialEq, Eq)]
11+
pub enum LiteralValue<'a> {
12+
/// A boolean literal value.
13+
Bool(bool),
14+
/// A string literal value. Includes both single-quoted and unquoted strings, includes hexadecimal strings.
15+
/// e.g., "abc", 'abc', '0x123ABC'
16+
Str(&'a str),
17+
/// A numeric literal value. e.g., "123", "-123.456", "0x123" or hexadecimal
18+
/// Store as string slice to preserve original form until evaluation phase.
19+
/// Conversion to specific type is done within chain context during evaluation.
20+
Number(&'a str),
21+
}
22+
23+
/// Represents the possible comparison operators that can be used in filter expressions.
24+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25+
pub enum ComparisonOperator {
26+
/// Equality operator (==)
27+
Eq,
28+
/// Inequality operator (!=)
29+
Ne,
30+
/// Greater than operator (>)
31+
Gt,
32+
/// Greater than or equal to operator (>=)
33+
Gte,
34+
/// Less than operator (<)
35+
Lt,
36+
/// Less than or equal to operator (<=)
37+
Lte,
38+
/// String/collection comparison operators:
39+
/// - StartsWith: Checks if the string/collection starts with a given item.
40+
StartsWith,
41+
/// - EndsWith: Checks if the string/collection ends with a given item.
42+
EndsWith,
43+
/// - Contains: Checks if the string/collection contains a given item.
44+
Contains,
45+
}
46+
47+
/// Represents the possible logical operators that can be used in filter expressions.
48+
#[derive(Debug, Clone, PartialEq, Eq)]
49+
pub enum LogicalOperator {
50+
/// Logical AND operator (&&)
51+
And,
52+
/// Logical OR operator (||)
53+
Or,
54+
}
55+
56+
/// Represents the possible accessors that can be used in filter expressions.
57+
/// Accessors are used to access elements in collections or properties in objects.
58+
#[derive(Debug, Clone, PartialEq, Eq)]
59+
pub enum Accessor<'a> {
60+
/// Accessor for a collection index (e.g., [0], [1], etc.)
61+
Index(usize),
62+
/// Accessor for a property name (e.g., .name, .age, etc.)
63+
Key(&'a str),
64+
}
65+
66+
#[derive(Debug, Clone, PartialEq, Eq)]
67+
pub struct VariablePath<'a> {
68+
pub base: &'a str,
69+
pub accessors: Vec<Accessor<'a>>,
70+
}
71+
72+
/// Represents the left side of a condition (LHS) in a filter expression.
73+
/// The left side can either be a simple variable name or a path to a variable.
74+
#[derive(Debug, Clone, PartialEq, Eq)]
75+
pub enum ConditionLeft<'a> {
76+
/// A simple variable name (e.g., "name", "age", etc.)
77+
/// This is a direct reference to a variable in the data structure.
78+
Simple(&'a str),
79+
/// A sequence of accessors that form a path to a variable (e.g., "person.name", "person[0].age", etc.)
80+
Path(VariablePath<'a>),
81+
}
82+
83+
impl<'a> ConditionLeft<'a> {
84+
/// Helper method get the base name of the variable or path.
85+
pub fn base_name(&self) -> &'a str {
86+
match self {
87+
ConditionLeft::Simple(name) => name,
88+
ConditionLeft::Path(path) => path.base,
89+
}
90+
}
91+
92+
/// Helper method to get the accessors of the variable path.
93+
/// If ConditionLeft is a simple variable, it returns an empty slice.
94+
/// If it is a path, it returns the accessors of that path.
95+
/// Used during evaluation to traverse nested structures.
96+
pub fn accessors(&self) -> &[Accessor] {
97+
match self {
98+
ConditionLeft::Simple(_) => &[],
99+
ConditionLeft::Path(path) => &path.accessors,
100+
}
101+
}
102+
}
103+
104+
/// Represents a condition in a filter expression.
105+
/// A condition consists of a left side (LHS), an operator, and a right side (RHS).
106+
#[derive(Debug, Clone, PartialEq, Eq)]
107+
pub struct Condition<'a> {
108+
/// The left side of the condition (LHS).
109+
/// This can be a simple variable name or a path to a variable.
110+
pub left: ConditionLeft<'a>,
111+
/// The operator used in the condition (e.g., ==, !=, >, <, etc.)
112+
pub operator: ComparisonOperator,
113+
/// The right side of the condition (RHS).
114+
pub right: LiteralValue<'a>,
115+
}
116+
117+
/// Represents a complete filter expression.
118+
/// An expression can be a single condition or a logical combination of multiple conditions.
119+
#[derive(Debug, Clone, PartialEq, Eq)]
120+
pub enum Expression<'a> {
121+
/// A simple condition (e.g., "age > 30")
122+
Condition(Condition<'a>),
123+
/// A logical combination of two expressions (e.g., "age > 30 && name == 'John'")
124+
/// `Box` is used to avoid infinite type recursion, as `Expression` can contain other `Expression`s.
125+
Logical {
126+
/// The left side sub-expression.
127+
left: Box<Expression<'a>>,
128+
/// The logical operator used to combine the two expressions: AND or OR.
129+
operator: LogicalOperator,
130+
/// The right side sub-expression.
131+
right: Box<Expression<'a>>,
132+
},
133+
}

0 commit comments

Comments
 (0)