openzeppelin_monitor/services/filter/expression/ast.rs
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)]
11pub 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)]
25pub 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)]
49pub 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)]
59pub 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)]
67pub 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)]
75pub 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
83impl<'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)]
107pub 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)]
120pub 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}