use crate::odata_filters::{CompareOperator, Expr, ParseError, Value};
use bigdecimal::BigDecimal;
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveTime, Utc};
use std::str::FromStr;
use uuid::Uuid;
pub fn parse_str(query: impl AsRef<str>) -> Result<Expr, ParseError> {
match odata_filter::parse_str(query.as_ref().trim()) {
Ok(expr) => expr,
Err(error) => Err(ParseError::Parsing(error.to_string())),
}
}
enum AfterValueExpr {
Compare(CompareOperator, Box<Expr>),
In(Vec<Expr>),
End,
}
peg::parser! {
grammar odata_filter() for str {
use super::{Expr, CompareOperator, Value, ParseError};
pub(super) rule parse_str() -> Result<Expr, ParseError>
= filter()
rule filter() -> Result<Expr, ParseError>
= or_expr()
rule or_expr() -> Result<Expr, ParseError>
= l:and_expr() rest:(_ "or" _ r:and_expr() { r })* {
rest.into_iter().fold(l, |acc, r| Ok(Expr::Or(Box::new(acc?), Box::new(r?))))
}
rule and_expr() -> Result<Expr, ParseError>
= l:unary_expr() rest:(_ "and" _ r:unary_expr() { r })* {
rest.into_iter().fold(l, |acc, r| Ok(Expr::And(Box::new(acc?), Box::new(r?))))
}
rule unary_expr() -> Result<Expr, ParseError>
= "not" _ e:unary_expr() { Ok(Expr::Not(Box::new(e?))) }
/ any_expr()
rule any_expr() -> Result<Expr, ParseError>
= "(" _ e:filter() _ ")" { e }
/ l:value_expr() _ r:after_value_expr() { Ok(match r? {
AfterValueExpr::Compare(op, r) => Expr::Compare(Box::new(l?), op, r),
AfterValueExpr::In(r) => Expr::In(Box::new(l?), r),
AfterValueExpr::End => l?,
}) }
rule after_value_expr() -> Result<AfterValueExpr, ParseError>
= op:comparison_op() _ r:value_expr() { Ok(AfterValueExpr::Compare(op, Box::new(r?))) }
/ "in" _ "(" _ r:filter_list() _ ")" { Ok(AfterValueExpr::In(r?)) }
/ { Ok(AfterValueExpr::End) }
rule value_expr() -> Result<Expr, ParseError>
= function_call()
/ v:value() { Ok(Expr::Value(v?)) }
/ i:identifier() { Ok(Expr::Identifier(i)) }
rule comparison_op() -> CompareOperator
= "eq" { CompareOperator::Equal }
/ "ne" { CompareOperator::NotEqual }
/ "gt" { CompareOperator::GreaterThan }
/ "ge" { CompareOperator::GreaterOrEqual }
/ "lt" { CompareOperator::LessThan }
/ "le" { CompareOperator::LessOrEqual }
rule function_call() -> Result<Expr, ParseError>
= f:identifier() _ "(" _ l:filter_list() _ ")" { Ok(Expr::Function(f, l?)) }
rule identifier() -> String
= s:$(['a'..='z'|'A'..='Z'|'_']['a'..='z'|'A'..='Z'|'_'|'0'..='9']*) { s.to_owned() }
rule value() -> Result<Value, ParseError>
= string_value()
/ datetime_value()
/ date_value()
/ time_value()
/ uuid_value()
/ number_value()
/ v:bool_value() { Ok(v) }
/ v:null_value() { Ok(v) }
rule bool_value() -> Value
= ['t'|'T']['r'|'R']['u'|'U']['e'|'E'] { Value::Bool(true) }
/ ['f'|'F']['a'|'A']['l'|'L']['s'|'S']['e'|'E'] { Value::Bool(false) }
rule number_value() -> Result<Value, ParseError>
= n:$(['+' | '-']? ['0'..='9']+ ("." ['0'..='9']*)?) { Ok(Value::Number(BigDecimal::from_str(n).map_err(|_| ParseError::ParsingNumber)?)) }
rule uuid_value() -> Result<Value, ParseError>
= id:$(hex()*<8> "-" hex()*<4> "-" hex()*<4> "-" hex()*<4> "-" hex()*<12> ) { Ok(Value::Uuid(Uuid::parse_str(id).map_err(|_| ParseError::ParsingUuid)?)) }
rule hex() -> char
= ['0'..='9'|'a'..='f'|'A'..='F']
rule time() -> Result<NaiveTime, ParseError>
= hm:$($(['0'..='9']*<1,2>) ":" $(['0'..='9']*<2>)) s:$(":" $(['0'..='9']*<2>))? ms:$("." $(['0'..='9']*<1,9>))? {
match (s, ms) {
(Some(s), Some(ms)) => NaiveTime::parse_from_str(&format!("{hm}{s}{ms}"), "%H:%M:%S%.f"),
(Some(s), None) => NaiveTime::parse_from_str(&format!("{hm}{s}"), "%H:%M:%S"),
(None, _) => NaiveTime::parse_from_str(hm, "%H:%M"),
}.map_err(|_| ParseError::ParsingTime)
}
rule time_value() -> Result<Value, ParseError>
= t:time() { Ok(Value::Time(t?)) }
rule date() -> Result<NaiveDate, ParseError>
= d:$($(['0'..='9']*<4>) "-" $(['0'..='9']*<2>) "-" $(['0'..='9']*<2>)) { NaiveDate::parse_from_str(d, "%Y-%m-%d").map_err(|_| ParseError::ParsingDate) }
rule date_value() -> Result<Value, ParseError>
= d:date() { Ok(Value::Date(d?)) }
rule timezone_name() -> Result<chrono_tz::Tz, ParseError>
= z:$(['a'..='z'|'A'..='Z'|'-'|'_'|'/'|'+']['a'..='z'|'A'..='Z'|'-'|'_'|'/'|'+'|'0'..='9']+) { z.parse::<chrono_tz::Tz>().map_err(|_| ParseError::ParsingTimeZoneNamed) }
rule timezone_offset() -> Result<FixedOffset, ParseError>
= "Z" { "+0000".parse().map_err(|_| ParseError::ParsingTimeZone) }
/ z:$($(['-'|'+']) $(['0'..='9']*<2>) ":"? $(['0'..='9']*<2>)) { z.parse().map_err(|_| ParseError::ParsingTimeZone) }
/ z:$($(['-'|'+']) $(['0'..='9']*<2>)) { format!("{z}00").parse().map_err(|_| ParseError::ParsingTimeZone) }
rule datetime() -> Result<DateTime<Utc>, ParseError>
= d:date() "T" t:time() z:timezone_offset() { Ok(d?.and_time(t?).and_local_timezone(z?).earliest().ok_or(ParseError::ParsingDateTime)?.to_utc()) }
/ d:date() "T" t:time() z:timezone_name() { Ok(d?.and_time(t?).and_local_timezone(z?).earliest().ok_or(ParseError::ParsingDateTime)?.to_utc()) }
rule datetime_value() -> Result<Value, ParseError>
= dt:datetime() { Ok(Value::DateTime(dt?)) }
rule string_value() -> Result<Value, ParseError>
= "'" s:quote_escaped_string_content()* "'" { Ok(Value::String(s.into_iter().collect::<Result<Vec<_>, _>>()?.into_iter().collect())) }
rule quote_escaped_string_content() -> Result<char, ParseError>
= "''" { Ok('\'') }
/ c:[^'\''] { Ok(c) }
rule null_value() -> Value
= ['n'|'N']['u'|'U']['l'|'L']['l'|'L'] { Value::Null }
rule value_list() -> Result<Vec<Expr>, ParseError>
= v:value_expr() ** ( _ "," _ ) { v.into_iter().collect() }
rule filter_list() -> Result<Vec<Expr>, ParseError>
= v:filter() ** ( _ "," _ ) { v.into_iter().collect() }
rule _()
= [' '|'\t'|'\n'|'\r']*
}
}