odata_params/filters/
parse.rs

1use super::{CompareOperator, Expr, ParseError, Value};
2use bigdecimal::BigDecimal;
3use chrono::{DateTime, FixedOffset, NaiveDate, NaiveTime, Utc};
4use std::str::FromStr;
5use uuid::Uuid;
6
7/// Parses an OData v4 `$filter` expression string into an `Expr` AST.
8///
9/// A result containing the parsed `Expr` on success, or an `Error` on failure.
10///
11/// ```
12/// use odata_params::filters::parse_str;
13///
14/// let filter = "name eq 'John' and isActive eq true";
15/// let result = parse_str(filter).expect("valid filter tree");
16/// ```
17pub fn parse_str(query: impl AsRef<str>) -> Result<Expr, ParseError> {
18    match odata_filter::parse_str(query.as_ref().trim()) {
19        Ok(expr) => expr,
20        Err(_error) => Err(ParseError::Parsing),
21    }
22}
23
24enum AfterValueExpr {
25    Compare(CompareOperator, Box<Expr>),
26    In(Vec<Expr>),
27    End,
28}
29
30peg::parser! {
31    /// Parses OData v4 `$filter` expressions.
32    grammar odata_filter() for str {
33        use super::{Expr, CompareOperator, Value, ParseError};
34
35        /// Entry point for parsing a filter expression string.
36        pub(super) rule parse_str() -> Result<Expr, ParseError>
37            = filter()
38
39        /// Parses a filter expression.
40        rule filter() -> Result<Expr, ParseError>
41            = "not" _ e:filter() { Ok(Expr::Not(Box::new(e?))) }
42            / l:any_expr() _ "or" _ r:filter() { Ok(Expr::Or(Box::new(l?), Box::new(r?))) }
43            / l:any_expr() _ "and" _ r:filter() { Ok(Expr::And(Box::new(l?), Box::new(r?))) }
44            / any_expr()
45
46        /// Parses any expression, including grouped expressions and value expressions.
47        rule any_expr() -> Result<Expr, ParseError>
48            = "(" _ e:filter() _ ")" { e }
49            / l:value_expr() _ r:after_value_expr() { Ok(match r? {
50                AfterValueExpr::Compare(op, r) => Expr::Compare(Box::new(l?), op, r),
51                AfterValueExpr::In(r) => Expr::In(Box::new(l?), r),
52                AfterValueExpr::End => l?,
53            }) }
54
55        /// Parses an expression that comes after a value.
56        rule after_value_expr() -> Result<AfterValueExpr, ParseError>
57            = op:comparison_op() _ r:value_expr() { Ok(AfterValueExpr::Compare(op, Box::new(r?))) }
58            / "in" _ "(" _ r:filter_list() _ ")" { Ok(AfterValueExpr::In(r?)) }
59            / { Ok(AfterValueExpr::End) }
60
61        /// Parses a value expression, which can be a function call, a value, or an identifier.
62        rule value_expr() -> Result<Expr, ParseError>
63            = function_call()
64            / v:value() { Ok(Expr::Value(v?)) }
65            / i:identifier() { Ok(Expr::Identifier(i)) }
66
67        /// Parses a comparison operator.
68        rule comparison_op() -> CompareOperator
69            = "eq" { CompareOperator::Equal }
70            / "ne" { CompareOperator::NotEqual }
71            / "gt" { CompareOperator::GreaterThan }
72            / "ge" { CompareOperator::GreaterOrEqual }
73            / "lt" { CompareOperator::LessThan }
74            / "le" { CompareOperator::LessOrEqual }
75
76        /// Parses a function call with a name and arguments.
77        rule function_call() -> Result<Expr, ParseError>
78            = f:identifier() _ "(" _ l:filter_list() _ ")" { Ok(Expr::Function(f, l?)) }
79
80        /// Parses an identifier.
81        rule identifier() -> String
82            = s:$(['a'..='z'|'A'..='Z'|'_']['a'..='z'|'A'..='Z'|'_'|'0'..='9']+) { s.to_string() }
83
84        /// Parses a value, which can be a string, datetime, date, time, number, boolean, or null.
85        rule value() -> Result<Value, ParseError>
86            = string_value()
87            / datetime_value()
88            / date_value()
89            / time_value()
90            / uuid_value()
91            / number_value()
92            / v:bool_value() { Ok(v) }
93            / v:null_value() { Ok(v) }
94
95        /// Parses a boolean value.
96        rule bool_value() -> Value
97            = ['t'|'T']['r'|'R']['u'|'U']['e'|'E'] { Value::Bool(true) }
98            / ['f'|'F']['a'|'A']['l'|'L']['s'|'S']['e'|'E'] { Value::Bool(false) }
99
100        /// Parses a numeric value.
101        rule number_value() -> Result<Value, ParseError>
102            = n:$(['0'..='9']+ ("." ['0'..='9']*)?) { Ok(Value::Number(BigDecimal::from_str(n).map_err(|_| ParseError::ParsingNumber)?)) }
103
104        /// Parses a uuid value.
105        rule uuid_value() -> Result<Value, ParseError>
106            = id:$(hex()*<8> "-" hex()*<4> "-" hex()*<4> "-" hex()*<4> "-" hex()*<12> ) { Ok(Value::Uuid(Uuid::parse_str(id).map_err(|_| ParseError::ParsingUuid)?)) }
107
108        /// Parses a single hexadecimal digit.
109        rule hex() -> char
110            = ['0'..='9'|'a'..='f'|'A'..='F']
111
112        /// Parses a time value in the format `HH:MM:SS` or `HH:MM`.
113        rule time() -> Result<NaiveTime, ParseError>
114            = hm:$($(['0'..='9']*<1,2>) ":" $(['0'..='9']*<2>)) s:$(":" $(['0'..='9']*<2>))? ms:$("." $(['0'..='9']*<1,9>))? {
115                match (s, ms) {
116                    (Some(s), Some(ms)) => NaiveTime::parse_from_str(&format!("{hm}{s}{ms}"), "%H:%M:%S%.f"),
117                    (Some(s), None) => NaiveTime::parse_from_str(&format!("{hm}{s}"), "%H:%M:%S"),
118                    (None, _) => NaiveTime::parse_from_str(hm, "%H:%M"),
119                }.map_err(|_| ParseError::ParsingTime)
120            }
121
122        /// Parses a time value.
123        rule time_value() -> Result<Value, ParseError>
124            = t:time() { Ok(Value::Time(t?)) }
125
126        /// Parses a date value in the format `YYYY-MM-DD`.
127        rule date() -> Result<NaiveDate, ParseError>
128            = d:$($(['0'..='9']*<4>) "-" $(['0'..='9']*<2>) "-" $(['0'..='9']*<2>)) { NaiveDate::parse_from_str(d, "%Y-%m-%d").map_err(|_| ParseError::ParsingDate) }
129
130        /// Parses a date value.
131        rule date_value() -> Result<Value, ParseError>
132            = d:date() { Ok(Value::Date(d?)) }
133
134        /// Parses a named timezone.
135        rule timezone_name() -> Result<chrono_tz::Tz, ParseError>
136            = z:$(['a'..='z'|'A'..='Z'|'-'|'_'|'/'|'+']['a'..='z'|'A'..='Z'|'-'|'_'|'/'|'+'|'0'..='9']+) { z.parse::<chrono_tz::Tz>().map_err(|_| ParseError::ParsingTimeZoneNamed) }
137
138        /// Parses a timezone offset.
139        rule timezone_offset() -> Result<FixedOffset, ParseError>
140            = "Z" { "+0000".parse().map_err(|_| ParseError::ParsingTimeZone) }
141            / z:$($(['-'|'+']) $(['0'..='9']*<2>) ":"? $(['0'..='9']*<2>)) { z.parse().map_err(|_| ParseError::ParsingTimeZone) }
142            / z:$($(['-'|'+']) $(['0'..='9']*<2>)) { format!("{z}00").parse().map_err(|_| ParseError::ParsingTimeZone) }
143
144        /// Parses a datetime value in the format `YYYY-MM-DDTHH:MM:SSZ` or `YYYY-MM-DDTHH:MM:SS+01:00`.
145        rule datetime() -> Result<DateTime<Utc>, ParseError>
146            = d:date() "T" t:time() z:timezone_offset() { Ok(d?.and_time(t?).and_local_timezone(z?).earliest().ok_or(ParseError::ParsingDateTime)?.to_utc()) }
147            / d:date() "T" t:time() z:timezone_name() { Ok(d?.and_time(t?).and_local_timezone(z?).earliest().ok_or(ParseError::ParsingDateTime)?.to_utc()) }
148
149        /// Parses a datetime value.
150        rule datetime_value() -> Result<Value, ParseError>
151            = dt:datetime() { Ok(Value::DateTime(dt?)) }
152
153        /// Parses a string value enclosed in single quotes.
154        rule string_value() -> Result<Value, ParseError>
155            = "'" s:quote_escaped_string_content()* "'" { Ok(Value::String(s.into_iter().collect::<Result<Vec<_>, _>>()?.into_iter().collect())) }
156
157        rule quote_escaped_string_content() -> Result<char, ParseError>
158            = r"\" e:escape_character() { e }
159            / c:[^'\''] { Ok(c) }
160
161        rule escape_character() -> Result<char, ParseError>
162            = "'" { Ok('\'') }
163            / "n" { Ok('\n') }
164            / "r" { Ok('\r') }
165            / "t" { Ok('\t') }
166            / r"\" { Ok('\\') }
167            / "u" sequence:$(hex()*<1,8>) {
168                u32::from_str_radix(sequence, 16).ok().and_then(char::from_u32).ok_or(ParseError::ParsingUnicodeCodePoint)
169            }
170
171        /// Parses a null value.
172        rule null_value() -> Value
173            = ['n'|'N']['u'|'U']['l'|'L']['l'|'L'] { Value::Null }
174
175        /// Parses a list of value expressions separated by commas.
176        rule value_list() -> Result<Vec<Expr>, ParseError>
177            = v:value_expr() ** ( _ "," _ ) { v.into_iter().collect() }
178
179        /// Parses a list of filter expressions separated by commas.
180        rule filter_list() -> Result<Vec<Expr>, ParseError>
181            = v:filter() ** ( _ "," _ ) { v.into_iter().collect() }
182
183        /// Matches zero or more whitespace characters.
184        rule _()
185            = [' '|'\t'|'\n'|'\r']*
186    }
187}