2021-10-22 07:15:42 +08:00
//! BNF grammar:
//!
//! ```text
2022-06-15 15:14:19 +08:00
//! filter = expression EOF
2021-10-22 07:15:42 +08:00
//! expression = or
2022-06-16 15:12:37 +08:00
//! or = and ("OR" WS+ and)*
//! and = not ("AND" WS+ not)*
//! not = ("NOT" WS+ not) | primary
2022-05-30 19:58:11 +08:00
//! primary = (WS* "(" WS* expression WS* ")" WS*) | geoRadius | in | condition | exists | not_exists | to
//! in = value "IN" WS* "[" value_list "]"
2022-06-16 15:12:37 +08:00
//! condition = value ("=" | "!=" | ">" | ">=" | "<" | "<=") value
2022-06-15 15:14:19 +08:00
//! exists = value "EXISTS"
2022-06-16 15:12:37 +08:00
//! not_exists = value "NOT" WS+ "EXISTS"
//! to = value value "TO" WS+ value
//! value = WS* ( word | singleQuoted | doubleQuoted) WS+
2022-05-30 19:58:11 +08:00
//! value_list = (value ("," value)* ","?)?
2021-10-22 07:15:42 +08:00
//! singleQuoted = "'" .* all but quotes "'"
2021-11-09 08:00:42 +08:00
//! doubleQuoted = "\"" .* all but double quotes "\""
2021-10-22 07:15:42 +08:00
//! word = (alphanumeric | _ | - | .)+
2022-06-16 15:12:37 +08:00
//! geoRadius = "_geoRadius(" WS* float WS* "," WS* float WS* "," float WS* ")"
2021-11-03 00:35:17 +08:00
//! ```
//!
//! Other BNF grammar used to handle some specific errors:
//! ```text
2022-06-14 22:42:09 +08:00
//! geoPoint = WS* "_geoPoint(" (float ",")* ")"
2021-10-22 07:15:42 +08:00
//! ```
2021-11-04 21:22:35 +08:00
//!
//! Specific errors:
//! ================
//! - If a user try to use a geoPoint, as a primary OR as a value we must throw an error.
//! ```text
//! field = _geoPoint(12, 13, 14)
//! field < 12 AND _geoPoint(1, 2)
//! ```
//!
2022-06-15 15:14:19 +08:00
//! - If a user try to use a geoRadius as a value we must throw an error.
2021-11-04 21:22:35 +08:00
//! ```text
//! field = _geoRadius(12, 13, 14)
//! ```
//!
2021-10-22 07:15:42 +08:00
2021-10-22 07:59:38 +08:00
mod condition ;
2021-11-03 00:35:17 +08:00
mod error ;
2021-10-22 07:59:38 +08:00
mod value ;
2021-11-03 00:35:17 +08:00
2021-10-22 07:59:38 +08:00
pub use condition ::{ parse_condition , parse_to , Condition } ;
2022-06-14 22:42:09 +08:00
use condition ::{ parse_exists , parse_not_exists } ;
2022-08-17 22:06:29 +08:00
use error ::{ cut_with_err , ExpectedValueKind , NomErrorExt } ;
2021-11-03 00:35:17 +08:00
pub use error ::{ Error , ErrorKind } ;
2021-10-22 07:15:42 +08:00
use nom ::branch ::alt ;
2021-10-22 07:59:38 +08:00
use nom ::bytes ::complete ::tag ;
2022-08-17 22:53:40 +08:00
use nom ::character ::complete ::{ char , multispace0 } ;
2022-05-30 19:58:11 +08:00
use nom ::combinator ::{ cut , eof , map , opt } ;
2021-10-22 07:15:42 +08:00
use nom ::multi ::{ many0 , separated_list1 } ;
use nom ::number ::complete ::recognize_float ;
2021-11-03 00:35:17 +08:00
use nom ::sequence ::{ delimited , preceded , terminated , tuple } ;
2021-11-03 03:27:07 +08:00
use nom ::Finish ;
2021-10-22 07:15:42 +08:00
use nom_locate ::LocatedSpan ;
2022-08-17 23:25:31 +08:00
use std ::fmt ::Debug ;
use std ::str ::FromStr ;
2021-10-22 07:59:38 +08:00
pub ( crate ) use value ::parse_value ;
2022-08-17 22:53:40 +08:00
use value ::word_exact ;
2021-10-22 07:15:42 +08:00
2021-11-03 00:35:17 +08:00
pub type Span < ' a > = LocatedSpan < & ' a str , & ' a str > ;
2021-10-22 07:15:42 +08:00
2021-11-03 03:27:07 +08:00
type IResult < ' a , Ret > = nom ::IResult < Span < ' a > , Ret , Error < ' a > > ;
2021-10-22 21:09:56 +08:00
2021-11-06 23:02:27 +08:00
#[ derive(Debug, Clone, Eq) ]
2021-12-20 23:18:15 +08:00
pub struct Token < ' a > {
/// The token in the original input, it should be used when possible.
span : Span < ' a > ,
/// If you need to modify the original input you can use the `value` field
/// to store your modified input.
value : Option < String > ,
}
2021-10-22 07:15:42 +08:00
2021-11-06 23:02:27 +08:00
impl < ' a > PartialEq for Token < ' a > {
fn eq ( & self , other : & Self ) -> bool {
2021-12-20 23:18:15 +08:00
self . span . fragment ( ) = = other . span . fragment ( )
2021-11-06 23:02:27 +08:00
}
}
2021-10-22 07:15:42 +08:00
impl < ' a > Token < ' a > {
2021-12-20 23:18:15 +08:00
pub fn new ( span : Span < ' a > , value : Option < String > ) -> Self {
Self { span , value }
}
2022-06-09 22:03:49 +08:00
pub fn lexeme ( & self ) -> & str {
& self . span
}
2021-12-20 23:18:15 +08:00
pub fn value ( & self ) -> & str {
self . value . as_ref ( ) . map_or ( & self . span , | value | value )
2021-10-22 07:15:42 +08:00
}
2021-11-05 00:24:55 +08:00
pub fn as_external_error ( & self , error : impl std ::error ::Error ) -> Error < ' a > {
2021-12-20 23:18:15 +08:00
Error ::new_from_external ( self . span , error )
2021-11-05 00:24:55 +08:00
}
2021-11-05 17:46:54 +08:00
pub fn parse < T > ( & self ) -> Result < T , Error >
where
T : FromStr ,
T ::Err : std ::error ::Error ,
{
2021-12-20 23:18:15 +08:00
self . span . parse ( ) . map_err ( | e | self . as_external_error ( e ) )
2021-11-05 17:46:54 +08:00
}
2021-10-22 07:15:42 +08:00
}
impl < ' a > From < Span < ' a > > for Token < ' a > {
fn from ( span : Span < ' a > ) -> Self {
2021-12-20 23:18:15 +08:00
Self { span , value : None }
2021-10-22 07:15:42 +08:00
}
}
#[ derive(Debug, Clone, PartialEq, Eq) ]
2021-10-22 07:59:38 +08:00
pub enum FilterCondition < ' a > {
2022-06-14 21:15:05 +08:00
Not ( Box < Self > ) ,
2021-10-22 07:59:38 +08:00
Condition { fid : Token < ' a > , op : Condition < ' a > } ,
2022-05-30 19:58:11 +08:00
In { fid : Token < ' a > , els : Vec < Token < ' a > > } ,
2022-06-21 00:46:57 +08:00
Or ( Vec < Self > ) ,
And ( Vec < Self > ) ,
2021-10-22 07:59:38 +08:00
GeoLowerThan { point : [ Token < ' a > ; 2 ] , radius : Token < ' a > } ,
2021-10-22 07:15:42 +08:00
}
2021-10-22 07:59:38 +08:00
impl < ' a > FilterCondition < ' a > {
2021-12-07 22:16:29 +08:00
/// Returns the first token found at the specified depth, `None` if no token at this depth.
pub fn token_at_depth ( & self , depth : usize ) -> Option < & Token > {
2021-12-07 00:35:20 +08:00
match self {
2021-12-07 22:16:29 +08:00
FilterCondition ::Condition { fid , .. } if depth = = 0 = > Some ( fid ) ,
2022-06-21 00:46:57 +08:00
FilterCondition ::Or ( subfilters ) = > {
2021-12-08 00:20:11 +08:00
let depth = depth . saturating_sub ( 1 ) ;
2022-06-21 00:46:57 +08:00
for f in subfilters . iter ( ) {
if let Some ( t ) = f . token_at_depth ( depth ) {
return Some ( t ) ;
}
}
None
2021-12-07 22:16:29 +08:00
}
2022-06-21 00:46:57 +08:00
FilterCondition ::And ( subfilters ) = > {
2021-12-08 00:20:11 +08:00
let depth = depth . saturating_sub ( 1 ) ;
2022-06-21 00:46:57 +08:00
for f in subfilters . iter ( ) {
if let Some ( t ) = f . token_at_depth ( depth ) {
return Some ( t ) ;
}
}
None
2021-12-07 22:16:29 +08:00
}
FilterCondition ::GeoLowerThan { point : [ point , _ ] , .. } if depth = = 0 = > Some ( point ) ,
_ = > None ,
2021-12-07 00:35:20 +08:00
}
}
2021-12-09 18:13:12 +08:00
pub fn parse ( input : & ' a str ) -> Result < Option < Self > , Error > {
2021-10-22 23:49:08 +08:00
if input . trim ( ) . is_empty ( ) {
2021-12-09 18:13:12 +08:00
return Ok ( None ) ;
2021-10-22 23:49:08 +08:00
}
2021-11-03 00:35:17 +08:00
let span = Span ::new_extra ( input , input ) ;
2021-12-09 18:13:12 +08:00
parse_filter ( span ) . finish ( ) . map ( | ( _rem , output ) | Some ( output ) )
2021-10-22 07:59:38 +08:00
}
2021-10-22 07:15:42 +08:00
}
2021-11-09 23:40:05 +08:00
/// remove OPTIONAL whitespaces before AND after the provided parser.
2021-11-03 03:27:07 +08:00
fn ws < ' a , O > ( inner : impl FnMut ( Span < ' a > ) -> IResult < O > ) -> impl FnMut ( Span < ' a > ) -> IResult < O > {
2021-10-22 07:59:38 +08:00
delimited ( multispace0 , inner , multispace0 )
}
2021-10-22 07:15:42 +08:00
2022-05-30 19:58:11 +08:00
/// value_list = (value ("," value)* ","?)?
fn parse_value_list < ' a > ( input : Span < ' a > ) -> IResult < Vec < Token < ' a > > > {
let ( input , first_value ) = opt ( parse_value ) ( input ) ? ;
if let Some ( first_value ) = first_value {
let value_list_el_parser = preceded ( ws ( tag ( " , " ) ) , parse_value ) ;
let ( input , mut values ) = many0 ( value_list_el_parser ) ( input ) ? ;
let ( input , _ ) = opt ( ws ( tag ( " , " ) ) ) ( input ) ? ;
values . insert ( 0 , first_value ) ;
Ok ( ( input , values ) )
} else {
Ok ( ( input , vec! [ ] ) )
}
}
/// in = value "IN" "[" value_list "]"
fn parse_in ( input : Span ) -> IResult < FilterCondition > {
let ( input , value ) = parse_value ( input ) ? ;
2022-08-17 22:53:40 +08:00
let ( input , _ ) = ws ( word_exact ( " IN " ) ) ( input ) ? ;
2022-05-30 19:58:11 +08:00
2022-06-15 16:13:34 +08:00
// everything after `IN` can be a failure
2022-07-18 23:09:52 +08:00
let ( input , _ ) =
cut_with_err ( tag ( " [ " ) , | _ | Error ::new_from_kind ( input , ErrorKind ::InOpeningBracket ) ) (
input ,
) ? ;
2022-06-15 16:13:34 +08:00
let ( input , content ) = cut ( parse_value_list ) ( input ) ? ;
2022-07-18 23:09:52 +08:00
2022-06-15 16:13:34 +08:00
// everything after `IN` can be a failure
let ( input , _ ) = cut_with_err ( tag ( " ] " ) , | _ | {
if eof ::< _ , ( ) > ( input ) . is_ok ( ) {
Error ::new_from_kind ( input , ErrorKind ::InClosingBracket )
} else {
2022-08-17 22:06:29 +08:00
let expected_value_kind = match parse_value ( input ) {
Err ( nom ::Err ::Error ( e ) ) = > match e . kind ( ) {
ErrorKind ::ReservedKeyword ( _ ) = > ExpectedValueKind ::ReservedKeyword ,
_ = > ExpectedValueKind ::Other ,
} ,
_ = > ExpectedValueKind ::Other ,
} ;
Error ::new_from_kind ( input , ErrorKind ::InExpectedValue ( expected_value_kind ) )
2022-06-15 16:13:34 +08:00
}
} ) ( input ) ? ;
2022-05-30 19:58:11 +08:00
let filter = FilterCondition ::In { fid : value , els : content } ;
Ok ( ( input , filter ) )
}
2022-06-15 15:58:47 +08:00
/// in = value "NOT" WS* "IN" "[" value_list "]"
fn parse_not_in ( input : Span ) -> IResult < FilterCondition > {
let ( input , value ) = parse_value ( input ) ? ;
2022-08-17 22:53:40 +08:00
let ( input , _ ) = word_exact ( " NOT " ) ( input ) ? ;
let ( input , _ ) = ws ( word_exact ( " IN " ) ) ( input ) ? ;
2022-06-15 15:58:47 +08:00
2022-06-15 16:13:34 +08:00
// everything after `IN` can be a failure
2022-07-18 23:09:52 +08:00
let ( input , _ ) =
cut_with_err ( tag ( " [ " ) , | _ | Error ::new_from_kind ( input , ErrorKind ::InOpeningBracket ) ) (
input ,
) ? ;
2022-06-15 16:13:34 +08:00
let ( input , content ) = cut ( parse_value_list ) ( input ) ? ;
2022-07-18 23:09:52 +08:00
2022-06-15 16:13:34 +08:00
// everything after `IN` can be a failure
2022-07-18 23:09:52 +08:00
let ( input , _ ) =
cut_with_err ( tag ( " ] " ) , | _ | Error ::new_from_kind ( input , ErrorKind ::InClosingBracket ) ) (
input ,
) ? ;
2022-06-15 16:13:34 +08:00
2022-06-15 15:58:47 +08:00
let filter = FilterCondition ::Not ( Box ::new ( FilterCondition ::In { fid : value , els : content } ) ) ;
Ok ( ( input , filter ) )
}
2022-05-30 19:58:11 +08:00
/// or = and ("OR" and)
2021-11-03 03:27:07 +08:00
fn parse_or ( input : Span ) -> IResult < FilterCondition > {
2022-06-21 00:46:57 +08:00
let ( input , first_filter ) = parse_and ( input ) ? ;
2021-11-04 21:22:35 +08:00
// if we found a `OR` then we MUST find something next
2022-08-17 22:53:40 +08:00
let ( input , mut ors ) = many0 ( preceded ( ws ( word_exact ( " OR " ) ) , cut ( parse_and ) ) ) ( input ) ? ;
2022-06-21 00:46:57 +08:00
let filter = if ors . is_empty ( ) {
first_filter
} else {
ors . insert ( 0 , first_filter ) ;
FilterCondition ::Or ( ors )
} ;
2021-10-22 07:15:42 +08:00
2022-06-21 00:46:57 +08:00
Ok ( ( input , filter ) )
2021-10-22 07:15:42 +08:00
}
2022-06-15 15:14:19 +08:00
/// and = not ("AND" not)*
2021-11-03 03:27:07 +08:00
fn parse_and ( input : Span ) -> IResult < FilterCondition > {
2022-06-21 00:46:57 +08:00
let ( input , first_filter ) = parse_not ( input ) ? ;
2021-11-04 21:22:35 +08:00
// if we found a `AND` then we MUST find something next
2022-08-17 22:53:40 +08:00
let ( input , mut ands ) = many0 ( preceded ( ws ( word_exact ( " AND " ) ) , cut ( parse_not ) ) ) ( input ) ? ;
2022-06-21 00:46:57 +08:00
let filter = if ands . is_empty ( ) {
first_filter
} else {
ands . insert ( 0 , first_filter ) ;
FilterCondition ::And ( ands )
} ;
Ok ( ( input , filter ) )
2021-10-22 07:15:42 +08:00
}
2022-06-16 15:12:37 +08:00
/// not = ("NOT" WS+ not) | primary
/// We can have multiple consecutive not, eg: `NOT NOT channel = mv`.
2021-11-09 23:47:54 +08:00
/// If we parse a `NOT` we MUST parse something behind.
2021-11-03 03:27:07 +08:00
fn parse_not ( input : Span ) -> IResult < FilterCondition > {
2022-06-16 15:12:37 +08:00
alt ( (
2022-08-17 22:53:40 +08:00
map ( preceded ( ws ( word_exact ( " NOT " ) ) , cut ( parse_not ) ) , | e | FilterCondition ::Not ( Box ::new ( e ) ) ) ,
2022-06-16 15:12:37 +08:00
parse_primary ,
) ) ( input )
2021-10-22 07:15:42 +08:00
}
2022-06-16 12:19:33 +08:00
/// geoRadius = WS* "_geoRadius(float WS* "," WS* float WS* "," WS* float)
2021-11-04 21:22:35 +08:00
/// If we parse `_geoRadius` we MUST parse the rest of the expression.
2021-11-03 03:27:07 +08:00
fn parse_geo_radius ( input : Span ) -> IResult < FilterCondition > {
2022-06-16 15:12:37 +08:00
// we want to allow space BEFORE the _geoRadius but not after
2021-11-04 21:22:35 +08:00
let parsed = preceded (
2022-08-17 22:53:40 +08:00
tuple ( ( multispace0 , word_exact ( " _geoRadius " ) ) ) ,
2021-11-09 23:40:05 +08:00
// if we were able to parse `_geoRadius` and can't parse the rest of the input we return a failure
2021-11-09 08:03:02 +08:00
cut ( delimited ( char ( '(' ) , separated_list1 ( tag ( " , " ) , ws ( recognize_float ) ) , char ( ')' ) ) ) ,
2021-11-04 21:22:35 +08:00
) ( input )
2021-11-04 23:20:53 +08:00
. map_err ( | e | e . map ( | _ | Error ::new_from_kind ( input , ErrorKind ::Geo ) ) ) ;
2021-10-22 07:15:42 +08:00
2021-11-04 21:22:35 +08:00
let ( input , args ) = parsed ? ;
2021-10-22 07:15:42 +08:00
if args . len ( ) ! = 3 {
2021-11-04 23:20:53 +08:00
return Err ( nom ::Err ::Failure ( Error ::new_from_kind ( input , ErrorKind ::Geo ) ) ) ;
2021-10-22 07:15:42 +08:00
}
let res = FilterCondition ::GeoLowerThan {
point : [ args [ 0 ] . into ( ) , args [ 1 ] . into ( ) ] ,
radius : args [ 2 ] . into ( ) ,
} ;
Ok ( ( input , res ) )
}
2022-06-16 12:19:33 +08:00
/// geoPoint = WS* "_geoPoint(float WS* "," WS* float WS* "," WS* float)
2021-11-04 21:22:35 +08:00
fn parse_geo_point ( input : Span ) -> IResult < FilterCondition > {
// we want to forbid space BEFORE the _geoPoint but not after
tuple ( (
multispace0 ,
tag ( " _geoPoint " ) ,
// if we were able to parse `_geoPoint` we are going to return a Failure whatever happens next.
2022-01-10 22:59:04 +08:00
cut ( delimited ( char ( '(' ) , separated_list1 ( tag ( " , " ) , ws ( recognize_float ) ) , char ( ')' ) ) ) ,
2021-11-04 21:22:35 +08:00
) ) ( input )
2021-11-04 23:20:53 +08:00
. map_err ( | e | e . map ( | _ | Error ::new_from_kind ( input , ErrorKind ::ReservedGeo ( " _geoPoint " ) ) ) ) ? ;
2021-11-09 23:40:05 +08:00
// if we succeeded we still return a `Failure` because geoPoints are not allowed
2021-11-04 23:20:53 +08:00
Err ( nom ::Err ::Failure ( Error ::new_from_kind ( input , ErrorKind ::ReservedGeo ( " _geoPoint " ) ) ) )
2021-11-04 21:22:35 +08:00
}
2022-08-17 22:06:29 +08:00
fn parse_error_reserved_keyword ( input : Span ) -> IResult < FilterCondition > {
match parse_condition ( input ) {
Ok ( result ) = > Ok ( result ) ,
Err ( nom ::Err ::Error ( inner ) | nom ::Err ::Failure ( inner ) ) = > match inner . kind ( ) {
ErrorKind ::ExpectedValue ( ExpectedValueKind ::ReservedKeyword ) = > {
return Err ( nom ::Err ::Failure ( inner ) ) ;
}
_ = > return Err ( nom ::Err ::Error ( inner ) ) ,
} ,
Err ( e ) = > {
return Err ( e ) ;
}
}
}
2022-06-16 15:12:37 +08:00
/// primary = (WS* "(" WS* expression WS* ")" WS*) | geoRadius | condition | exists | not_exists | to
2021-11-03 03:27:07 +08:00
fn parse_primary ( input : Span ) -> IResult < FilterCondition > {
2021-10-22 07:15:42 +08:00
alt ( (
2021-11-04 21:22:35 +08:00
// if we find a first parenthesis, then we must parse an expression and find the closing parenthesis
delimited (
ws ( char ( '(' ) ) ,
cut ( parse_expression ) ,
cut_with_err ( ws ( char ( ')' ) ) , | c | {
2021-11-04 23:20:53 +08:00
Error ::new_from_kind ( input , ErrorKind ::MissingClosingDelimiter ( c . char ( ) ) )
2021-11-04 21:22:35 +08:00
} ) ,
) ,
2021-11-09 08:03:02 +08:00
parse_geo_radius ,
2022-05-30 19:58:11 +08:00
parse_in ,
2022-06-15 15:58:47 +08:00
parse_not_in ,
2021-11-09 08:03:02 +08:00
parse_condition ,
2022-06-14 22:42:09 +08:00
parse_exists ,
parse_not_exists ,
2021-11-09 08:03:02 +08:00
parse_to ,
2021-11-04 21:22:35 +08:00
// the next lines are only for error handling and are written at the end to have the less possible performance impact
2021-11-09 08:03:02 +08:00
parse_geo_point ,
2022-08-17 22:06:29 +08:00
parse_error_reserved_keyword ,
2021-10-22 07:15:42 +08:00
) ) ( input )
2021-11-04 21:22:35 +08:00
// if the inner parsers did not match enough information to return an accurate error
2021-11-04 23:20:53 +08:00
. map_err ( | e | e . map_err ( | _ | Error ::new_from_kind ( input , ErrorKind ::InvalidPrimary ) ) )
2022-06-15 16:13:34 +08:00
// TODO: if the filter starts with a reserved keyword that is not NOT, then we should return the reserved keyword error
// TODO: if the filter is x = reserved, idem
2021-10-22 07:15:42 +08:00
}
/// expression = or
2021-11-03 03:27:07 +08:00
pub fn parse_expression ( input : Span ) -> IResult < FilterCondition > {
2021-10-22 07:15:42 +08:00
parse_or ( input )
}
2022-06-15 15:14:19 +08:00
/// filter = expression EOF
2021-11-03 03:27:07 +08:00
pub fn parse_filter ( input : Span ) -> IResult < FilterCondition > {
2021-11-03 00:35:17 +08:00
terminated ( parse_expression , eof ) ( input )
}
2021-10-22 07:15:42 +08:00
#[ cfg(test) ]
2021-10-22 07:59:38 +08:00
pub mod tests {
2021-10-22 07:15:42 +08:00
use super ::* ;
/// Create a raw [Token]. You must specify the string that appear BEFORE your element followed by your element
2021-10-22 07:59:38 +08:00
pub fn rtok < ' a > ( before : & ' a str , value : & ' a str ) -> Token < ' a > {
2021-10-22 07:15:42 +08:00
// if the string is empty we still need to return 1 for the line number
let lines = before . is_empty ( ) . then ( | | 1 ) . unwrap_or_else ( | | before . lines ( ) . count ( ) ) ;
let offset = before . chars ( ) . count ( ) ;
2021-11-03 00:35:17 +08:00
// the extra field is not checked in the tests so we can set it to nothing
unsafe { Span ::new_from_raw_offset ( offset , lines as u32 , value , " " ) } . into ( )
2021-10-22 07:15:42 +08:00
}
#[ test ]
fn parse ( ) {
use FilterCondition as Fc ;
2022-08-17 23:25:31 +08:00
macro_rules ! p {
( $s :literal ) = > {
Fc ::parse ( $s ) . unwrap ( ) . unwrap ( )
} ;
2021-10-22 07:15:42 +08:00
}
2022-08-17 23:25:31 +08:00
// Test equal
insta ::assert_display_snapshot! ( p! ( " channel = Ponce " ) , @ " {channel} = {Ponce} " ) ;
insta ::assert_display_snapshot! ( p! ( " subscribers = 12 " ) , @ " {subscribers} = {12} " ) ;
insta ::assert_display_snapshot! ( p! ( " channel = 'Mister Mv' " ) , @ " {channel} = {Mister Mv} " ) ;
insta ::assert_display_snapshot! ( p! ( " channel = \" Mister Mv \" " ) , @ " {channel} = {Mister Mv} " ) ;
insta ::assert_display_snapshot! ( p! ( " 'dog race' = Borzoi " ) , @ " {dog race} = {Borzoi} " ) ;
insta ::assert_display_snapshot! ( p! ( " \" dog race \" = Chusky " ) , @ " {dog race} = {Chusky} " ) ;
insta ::assert_display_snapshot! ( p! ( " \" dog race \" = \" Bernese Mountain \" " ) , @ " {dog race} = {Bernese Mountain} " ) ;
insta ::assert_display_snapshot! ( p! ( " 'dog race' = 'Bernese Mountain' " ) , @ " {dog race} = {Bernese Mountain} " ) ;
insta ::assert_display_snapshot! ( p! ( " \" dog race \" = 'Bernese Mountain' " ) , @ " {dog race} = {Bernese Mountain} " ) ;
// Test IN
insta ::assert_display_snapshot! ( p! ( " colour IN[] " ) , @ " {colour} IN[] " ) ;
insta ::assert_display_snapshot! ( p! ( " colour IN[green] " ) , @ " {colour} IN[{green}, ] " ) ;
insta ::assert_display_snapshot! ( p! ( " colour IN[green,] " ) , @ " {colour} IN[{green}, ] " ) ;
insta ::assert_display_snapshot! ( p! ( " colour NOT IN[green,blue] " ) , @ " NOT ({colour} IN[{green}, {blue}, ]) " ) ;
insta ::assert_display_snapshot! ( p! ( " colour IN [ green , blue , ] " ) , @ " {colour} IN[{green}, {blue}, ] " ) ;
// Test conditions
insta ::assert_display_snapshot! ( p! ( " channel != ponce " ) , @ " {channel} != {ponce} " ) ;
insta ::assert_display_snapshot! ( p! ( " NOT channel = ponce " ) , @ " NOT ({channel} = {ponce}) " ) ;
insta ::assert_display_snapshot! ( p! ( " subscribers < 1000 " ) , @ " {subscribers} < {1000} " ) ;
insta ::assert_display_snapshot! ( p! ( " subscribers > 1000 " ) , @ " {subscribers} > {1000} " ) ;
insta ::assert_display_snapshot! ( p! ( " subscribers <= 1000 " ) , @ " {subscribers} <= {1000} " ) ;
insta ::assert_display_snapshot! ( p! ( " subscribers >= 1000 " ) , @ " {subscribers} >= {1000} " ) ;
insta ::assert_display_snapshot! ( p! ( " subscribers <= 1000 " ) , @ " {subscribers} <= {1000} " ) ;
insta ::assert_display_snapshot! ( p! ( " subscribers 100 TO 1000 " ) , @ " {subscribers} {100} TO {1000} " ) ;
// Test NOT + EXISTS
insta ::assert_display_snapshot! ( p! ( " subscribers EXISTS " ) , @ " {subscribers} EXISTS " ) ;
insta ::assert_display_snapshot! ( p! ( " NOT subscribers < 1000 " ) , @ " NOT ({subscribers} < {1000}) " ) ;
insta ::assert_display_snapshot! ( p! ( " NOT subscribers EXISTS " ) , @ " NOT ({subscribers} EXISTS) " ) ;
insta ::assert_display_snapshot! ( p! ( " subscribers NOT EXISTS " ) , @ " NOT ({subscribers} EXISTS) " ) ;
insta ::assert_display_snapshot! ( p! ( " NOT subscribers NOT EXISTS " ) , @ " NOT (NOT ({subscribers} EXISTS)) " ) ;
insta ::assert_display_snapshot! ( p! ( " subscribers NOT EXISTS " ) , @ " NOT ({subscribers} EXISTS) " ) ;
insta ::assert_display_snapshot! ( p! ( " NOT subscribers 100 TO 1000 " ) , @ " NOT ({subscribers} {100} TO {1000}) " ) ;
// Test geo radius
insta ::assert_display_snapshot! ( p! ( " _geoRadius(12, 13, 14) " ) , @ " _geoRadius({12}, {13}, {14}) " ) ;
insta ::assert_display_snapshot! ( p! ( " NOT _geoRadius(12, 13, 14) " ) , @ " NOT (_geoRadius({12}, {13}, {14})) " ) ;
// Test OR + AND
insta ::assert_display_snapshot! ( p! ( " channel = ponce AND 'dog race' != 'bernese mountain' " ) , @ " AND[{channel} = {ponce}, {dog race} != {bernese mountain}, ] " ) ;
insta ::assert_display_snapshot! ( p! ( " channel = ponce OR 'dog race' != 'bernese mountain' " ) , @ " OR[{channel} = {ponce}, {dog race} != {bernese mountain}, ] " ) ;
insta ::assert_display_snapshot! ( p! ( " channel = ponce AND 'dog race' != 'bernese mountain' OR subscribers > 1000 " ) , @ " OR[AND[{channel} = {ponce}, {dog race} != {bernese mountain}, ], {subscribers} > {1000}, ] " ) ;
insta ::assert_display_snapshot! (
p! ( " channel = ponce AND 'dog race' != 'bernese mountain' OR subscribers > 1000 OR colour = red OR colour = blue AND size = 7 " ) ,
@ " OR[AND[{channel} = {ponce}, {dog race} != {bernese mountain}, ], {subscribers} > {1000}, {colour} = {red}, AND[{colour} = {blue}, {size} = {7}, ], ] "
) ;
// test parentheses
insta ::assert_display_snapshot! ( p! ( " channel = ponce AND ( 'dog race' != 'bernese mountain' OR subscribers > 1000 ) " ) , @ " AND[{channel} = {ponce}, OR[{dog race} != {bernese mountain}, {subscribers} > {1000}, ], ] " ) ;
insta ::assert_display_snapshot! ( p! ( " (channel = ponce AND 'dog race' != 'bernese mountain' OR subscribers > 1000) AND _geoRadius(12, 13, 14) " ) , @ " AND[OR[AND[{channel} = {ponce}, {dog race} != {bernese mountain}, ], {subscribers} > {1000}, ], _geoRadius({12}, {13}, {14}), ] " ) ;
2021-10-22 07:15:42 +08:00
}
2021-11-03 00:35:17 +08:00
#[ test ]
fn error ( ) {
use FilterCondition as Fc ;
2022-08-17 23:25:31 +08:00
macro_rules ! p {
( $s :literal ) = > {
Fc ::parse ( $s ) . unwrap_err ( ) . to_string ( )
} ;
2021-11-03 00:35:17 +08:00
}
2022-08-17 23:25:31 +08:00
insta ::assert_display_snapshot! ( p! ( " channel = Ponce = 12 " ) , @ r ###"
Found unexpected characters at the end of the filter : ` = 12 ` . You probably forgot an ` OR ` or an ` AND ` rule .
17 :21 channel = Ponce = 12
" ###);
insta ::assert_display_snapshot! ( p! ( " channel = " ) , @ r ###"
Was expecting a value but instead got nothing .
14 :14 channel =
" ###);
insta ::assert_display_snapshot! ( p! ( " channel = 🐻 " ) , @ r ###"
Was expecting a value but instead got ` 🐻 ` .
11 :12 channel = 🐻
" ###);
insta ::assert_display_snapshot! ( p! ( " channel = 🐻 AND followers < 100 " ) , @ r ###"
Was expecting a value but instead got ` 🐻 ` .
11 :12 channel = 🐻 AND followers < 100
" ###);
insta ::assert_display_snapshot! ( p! ( " 'OR' " ) , @ r ###"
Was expecting an operation ` = ` , ` ! = ` , ` > = ` , ` > ` , ` < = ` , ` < ` , ` TO ` , ` EXISTS ` , ` NOT EXISTS ` , or ` _geoRadius ` at ` \ ' OR \ ' ` .
1 :5 ' OR '
" ###);
insta ::assert_display_snapshot! ( p! ( " OR " ) , @ r ###"
Was expecting a value but instead got ` OR ` , which is a reserved keyword . To use ` OR ` as a field name or a value , surround it by quotes .
1 :3 OR
" ###);
insta ::assert_display_snapshot! ( p! ( " channel Ponce " ) , @ r ###"
Was expecting an operation ` = ` , ` ! = ` , ` > = ` , ` > ` , ` < = ` , ` < ` , ` TO ` , ` EXISTS ` , ` NOT EXISTS ` , or ` _geoRadius ` at ` channel Ponce ` .
1 :14 channel Ponce
" ###);
insta ::assert_display_snapshot! ( p! ( " channel = Ponce OR " ) , @ r ###"
Was expecting an operation ` = ` , ` ! = ` , ` > = ` , ` > ` , ` < = ` , ` < ` , ` TO ` , ` EXISTS ` , ` NOT EXISTS ` , or ` _geoRadius ` but instead got nothing .
19 :19 channel = Ponce OR
" ###);
insta ::assert_display_snapshot! ( p! ( " _geoRadius " ) , @ r ###"
The ` _geoRadius ` filter expects three arguments : ` _geoRadius ( latitude , longitude , radius ) ` .
1 :11 _geoRadius
" ###);
insta ::assert_display_snapshot! ( p! ( " _geoRadius = 12 " ) , @ r ###"
The ` _geoRadius ` filter expects three arguments : ` _geoRadius ( latitude , longitude , radius ) ` .
1 :16 _geoRadius = 12
" ###);
insta ::assert_display_snapshot! ( p! ( " _geoPoint(12, 13, 14) " ) , @ r ###"
` _geoPoint ` is a reserved keyword and thus can ' t be used as a filter expression . Use the ` _geoRadius ( latitude , longitude , distance ) built - in rule to filter on ` _geo ` coordinates .
1 :22 _geoPoint ( 12 , 13 , 14 )
" ###);
insta ::assert_display_snapshot! ( p! ( " position <= _geoPoint(12, 13, 14) " ) , @ r ###"
` _geoPoint ` is a reserved keyword and thus can ' t be used as a filter expression . Use the ` _geoRadius ( latitude , longitude , distance ) built - in rule to filter on ` _geo ` coordinates .
13 :34 position < = _geoPoint ( 12 , 13 , 14 )
" ###);
insta ::assert_display_snapshot! ( p! ( " position <= _geoRadius(12, 13, 14) " ) , @ r ###"
The ` _geoRadius ` filter is an operation and can ' t be used as a value .
13 :35 position < = _geoRadius ( 12 , 13 , 14 )
" ###);
insta ::assert_display_snapshot! ( p! ( " channel = 'ponce " ) , @ r ###"
Expression ` \ ' ponce ` is missing the following closing delimiter : ` ' ` .
11 :17 channel = ' ponce
" ###);
insta ::assert_display_snapshot! ( p! ( " channel = \" ponce " ) , @ r ###"
Expression ` \ " ponce` is missing the following closing delimiter: ` " ` .
11 :17 channel = " ponce
" ###);
insta ::assert_display_snapshot! ( p! ( " channel = mv OR (followers >= 1000 " ) , @ r ###"
Expression ` ( followers > = 1000 ` is missing the following closing delimiter : ` ) ` .
17 :35 channel = mv OR ( followers > = 1000
" ###);
insta ::assert_display_snapshot! ( p! ( " channel = mv OR followers >= 1000) " ) , @ r ###"
Found unexpected characters at the end of the filter : ` ) ` . You probably forgot an ` OR ` or an ` AND ` rule .
34 :35 channel = mv OR followers > = 1000 )
" ###);
insta ::assert_display_snapshot! ( p! ( " colour NOT EXIST " ) , @ r ###"
Was expecting an operation ` = ` , ` ! = ` , ` > = ` , ` > ` , ` < = ` , ` < ` , ` TO ` , ` EXISTS ` , ` NOT EXISTS ` , or ` _geoRadius ` at ` colour NOT EXIST ` .
1 :17 colour NOT EXIST
" ###);
insta ::assert_display_snapshot! ( p! ( " subscribers 100 TO1000 " ) , @ r ###"
Was expecting an operation ` = ` , ` ! = ` , ` > = ` , ` > ` , ` < = ` , ` < ` , ` TO ` , ` EXISTS ` , ` NOT EXISTS ` , or ` _geoRadius ` at ` subscribers 100 TO1000 ` .
1 :23 subscribers 100 TO1000
" ###);
insta ::assert_display_snapshot! ( p! ( " channel = ponce ORdog != 'bernese mountain' " ) , @ r ###"
Found unexpected characters at the end of the filter : ` ORdog ! = \ ' bernese mountain \ ' ` . You probably forgot an ` OR ` or an ` AND ` rule .
17 :44 channel = ponce ORdog ! = ' bernese mountain '
" ###);
insta ::assert_display_snapshot! ( p! ( " colour IN blue, green] " ) , @ r ###"
Expected ` [ ` after ` IN ` keyword .
11 :23 colour IN blue , green ]
" ###);
insta ::assert_display_snapshot! ( p! ( " colour IN [blue, green, 'blue' > 2] " ) , @ r ###"
Expected only comma - separated field names inside ` IN [ .. ] ` but instead found ` > 2 ] ` .
32 :36 colour IN [ blue , green , ' blue ' > 2 ]
" ###);
insta ::assert_display_snapshot! ( p! ( " colour IN [blue, green, AND] " ) , @ r ###"
Expected only comma - separated field names inside ` IN [ .. ] ` but instead found ` AND ] ` .
25 :29 colour IN [ blue , green , AND ]
" ###);
insta ::assert_display_snapshot! ( p! ( " colour IN [blue, green " ) , @ r ###"
Expected matching ` ] ` after the list of field names given to ` IN [ `
23 :23 colour IN [ blue , green
" ###);
insta ::assert_display_snapshot! ( p! ( " colour IN ['blue, green " ) , @ r ###"
Expression ` \ ' blue , green ` is missing the following closing delimiter : ` ' ` .
12 :24 colour IN [ ' blue , green
" ###);
insta ::assert_display_snapshot! ( p! ( " x = EXISTS " ) , @ r ###"
Was expecting a value but instead got ` EXISTS ` , which is a reserved keyword . To use ` EXISTS ` as a field name or a value , surround it by quotes .
5 :11 x = EXISTS
" ###);
insta ::assert_display_snapshot! ( p! ( " AND = 8 " ) , @ r ###"
Was expecting a value but instead got ` AND ` , which is a reserved keyword . To use ` AND ` as a field name or a value , surround it by quotes .
1 :4 AND = 8
" ###);
2021-11-03 00:35:17 +08:00
}
2021-12-07 00:35:20 +08:00
#[ test ]
fn depth ( ) {
2021-12-09 18:13:12 +08:00
let filter = FilterCondition ::parse ( " account_ids=1 OR account_ids=2 OR account_ids=3 OR account_ids=4 OR account_ids=5 OR account_ids=6 " ) . unwrap ( ) . unwrap ( ) ;
2022-06-21 00:46:57 +08:00
assert! ( filter . token_at_depth ( 1 ) . is_some ( ) ) ;
assert! ( filter . token_at_depth ( 2 ) . is_none ( ) ) ;
let filter = FilterCondition ::parse ( " (account_ids=1 OR (account_ids=2 AND account_ids=3) OR (account_ids=4 AND account_ids=5) OR account_ids=6) " ) . unwrap ( ) . unwrap ( ) ;
assert! ( filter . token_at_depth ( 2 ) . is_some ( ) ) ;
assert! ( filter . token_at_depth ( 3 ) . is_none ( ) ) ;
let filter = FilterCondition ::parse ( " account_ids=1 OR account_ids=2 AND account_ids=3 OR account_ids=4 AND account_ids=5 OR account_ids=6 " ) . unwrap ( ) . unwrap ( ) ;
assert! ( filter . token_at_depth ( 2 ) . is_some ( ) ) ;
assert! ( filter . token_at_depth ( 3 ) . is_none ( ) ) ;
2021-12-07 00:35:20 +08:00
}
2021-10-22 07:15:42 +08:00
}
2022-08-17 23:25:31 +08:00
impl < ' a > std ::fmt ::Display for FilterCondition < ' a > {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
match self {
FilterCondition ::Not ( filter ) = > {
write! ( f , " NOT ({filter}) " )
}
FilterCondition ::Condition { fid , op } = > {
write! ( f , " {fid} {op} " )
}
FilterCondition ::In { fid , els } = > {
write! ( f , " {fid} IN[ " ) ? ;
for el in els {
write! ( f , " {el}, " ) ? ;
}
write! ( f , " ] " )
}
FilterCondition ::Or ( els ) = > {
write! ( f , " OR[ " ) ? ;
for el in els {
write! ( f , " {el}, " ) ? ;
}
write! ( f , " ] " )
}
FilterCondition ::And ( els ) = > {
write! ( f , " AND[ " ) ? ;
for el in els {
write! ( f , " {el}, " ) ? ;
}
write! ( f , " ] " )
}
FilterCondition ::GeoLowerThan { point , radius } = > {
write! ( f , " _geoRadius({}, {}, {}) " , point [ 0 ] , point [ 1 ] , radius )
}
}
}
}
impl < ' a > std ::fmt ::Display for Condition < ' a > {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
match self {
Condition ::GreaterThan ( token ) = > write! ( f , " > {token} " ) ,
Condition ::GreaterThanOrEqual ( token ) = > write! ( f , " >= {token} " ) ,
Condition ::Equal ( token ) = > write! ( f , " = {token} " ) ,
Condition ::NotEqual ( token ) = > write! ( f , " != {token} " ) ,
Condition ::Exists = > write! ( f , " EXISTS " ) ,
Condition ::LowerThan ( token ) = > write! ( f , " < {token} " ) ,
Condition ::LowerThanOrEqual ( token ) = > write! ( f , " <= {token} " ) ,
Condition ::Between { from , to } = > write! ( f , " {from} TO {to} " ) ,
}
}
}
impl < ' a > std ::fmt ::Display for Token < ' a > {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
write! ( f , " {{{}}} " , self . value ( ) )
}
}