2021-10-22 01:15:42 +02:00
//! BNF grammar:
//!
//! ```text
2022-06-15 09:14:19 +02:00
//! filter = expression EOF
2021-10-22 01:15:42 +02:00
//! expression = or
2022-06-16 09:12:37 +02:00
//! or = and ("OR" WS+ and)*
//! and = not ("AND" WS+ not)*
//! not = ("NOT" WS+ not) | primary
//! primary = (WS* "(" WS* expression WS* ")" WS*) | geoRadius | condition | exists | not_exists | to
//! condition = value ("=" | "!=" | ">" | ">=" | "<" | "<=") value
2022-06-15 09:14:19 +02:00
//! exists = value "EXISTS"
2022-06-16 09:12:37 +02:00
//! not_exists = value "NOT" WS+ "EXISTS"
//! to = value value "TO" WS+ value
//! value = WS* ( word | singleQuoted | doubleQuoted) WS+
2021-10-22 01:15:42 +02:00
//! singleQuoted = "'" .* all but quotes "'"
2021-11-09 01:00:42 +01:00
//! doubleQuoted = "\"" .* all but double quotes "\""
2021-10-22 01:15:42 +02:00
//! word = (alphanumeric | _ | - | .)+
2022-06-16 09:12:37 +02:00
//! geoRadius = "_geoRadius(" WS* float WS* "," WS* float WS* "," float WS* ")"
2021-11-02 17:35:17 +01:00
//! ```
//!
//! Other BNF grammar used to handle some specific errors:
//! ```text
2022-06-14 16:42:09 +02:00
//! geoPoint = WS* "_geoPoint(" (float ",")* ")"
2021-10-22 01:15:42 +02:00
//! ```
2021-11-04 14:22:35 +01: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 09:14:19 +02:00
//! - If a user try to use a geoRadius as a value we must throw an error.
2021-11-04 14:22:35 +01:00
//! ```text
//! field = _geoRadius(12, 13, 14)
//! ```
//!
2021-10-22 01:15:42 +02:00
2021-10-22 01:59:38 +02:00
mod condition ;
2021-11-02 17:35:17 +01:00
mod error ;
2021-10-22 01:59:38 +02:00
mod value ;
2021-11-02 17:35:17 +01:00
2021-10-22 01:15:42 +02:00
use std ::fmt ::Debug ;
2021-11-05 10:46:54 +01:00
use std ::str ::FromStr ;
2021-10-22 01:15:42 +02:00
2021-10-22 01:59:38 +02:00
pub use condition ::{ parse_condition , parse_to , Condition } ;
2022-06-14 16:42:09 +02:00
use condition ::{ parse_exists , parse_not_exists } ;
2021-11-09 00:50:15 +01:00
use error ::{ cut_with_err , NomErrorExt } ;
2021-11-02 17:35:17 +01:00
pub use error ::{ Error , ErrorKind } ;
2021-10-22 01:15:42 +02:00
use nom ::branch ::alt ;
2021-10-22 01:59:38 +02:00
use nom ::bytes ::complete ::tag ;
2022-06-16 09:12:37 +02:00
use nom ::character ::complete ::{ char , multispace0 , multispace1 } ;
2021-11-02 17:35:17 +01:00
use nom ::combinator ::{ cut , eof , map } ;
2021-10-22 01:15:42 +02:00
use nom ::multi ::{ many0 , separated_list1 } ;
use nom ::number ::complete ::recognize_float ;
2021-11-02 17:35:17 +01:00
use nom ::sequence ::{ delimited , preceded , terminated , tuple } ;
2021-11-02 20:27:07 +01:00
use nom ::Finish ;
2021-10-22 01:15:42 +02:00
use nom_locate ::LocatedSpan ;
2021-10-22 01:59:38 +02:00
pub ( crate ) use value ::parse_value ;
2021-10-22 01:15:42 +02:00
2021-11-02 17:35:17 +01:00
pub type Span < ' a > = LocatedSpan < & ' a str , & ' a str > ;
2021-10-22 01:15:42 +02:00
2021-11-02 20:27:07 +01:00
type IResult < ' a , Ret > = nom ::IResult < Span < ' a > , Ret , Error < ' a > > ;
2021-10-22 15:09:56 +02:00
2021-11-06 16:02:27 +01:00
#[ derive(Debug, Clone, Eq) ]
2021-12-20 16:18:15 +01: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 01:15:42 +02:00
2021-11-06 16:02:27 +01:00
impl < ' a > PartialEq for Token < ' a > {
fn eq ( & self , other : & Self ) -> bool {
2021-12-20 16:18:15 +01:00
self . span . fragment ( ) = = other . span . fragment ( )
2021-11-06 16:02:27 +01:00
}
}
2021-10-22 01:15:42 +02:00
impl < ' a > Token < ' a > {
2021-12-20 16:18:15 +01:00
pub fn new ( span : Span < ' a > , value : Option < String > ) -> Self {
Self { span , value }
}
2022-06-09 16:03:49 +02:00
pub fn lexeme ( & self ) -> & str {
& self . span
}
2021-12-20 16:18:15 +01:00
pub fn value ( & self ) -> & str {
self . value . as_ref ( ) . map_or ( & self . span , | value | value )
2021-10-22 01:15:42 +02:00
}
2021-11-04 17:24:55 +01:00
pub fn as_external_error ( & self , error : impl std ::error ::Error ) -> Error < ' a > {
2021-12-20 16:18:15 +01:00
Error ::new_from_external ( self . span , error )
2021-11-04 17:24:55 +01:00
}
2021-11-05 10:46:54 +01:00
pub fn parse < T > ( & self ) -> Result < T , Error >
where
T : FromStr ,
T ::Err : std ::error ::Error ,
{
2021-12-20 16:18:15 +01:00
self . span . parse ( ) . map_err ( | e | self . as_external_error ( e ) )
2021-11-05 10:46:54 +01:00
}
2021-10-22 01:15:42 +02:00
}
impl < ' a > From < Span < ' a > > for Token < ' a > {
fn from ( span : Span < ' a > ) -> Self {
2021-12-20 16:18:15 +01:00
Self { span , value : None }
2021-10-22 01:15:42 +02:00
}
}
#[ derive(Debug, Clone, PartialEq, Eq) ]
2021-10-22 01:59:38 +02:00
pub enum FilterCondition < ' a > {
Condition { fid : Token < ' a > , op : Condition < ' a > } ,
Or ( Box < Self > , Box < Self > ) ,
And ( Box < Self > , Box < Self > ) ,
GeoLowerThan { point : [ Token < ' a > ; 2 ] , radius : Token < ' a > } ,
GeoGreaterThan { point : [ Token < ' a > ; 2 ] , radius : Token < ' a > } ,
2021-10-22 01:15:42 +02:00
}
2021-10-22 01:59:38 +02:00
impl < ' a > FilterCondition < ' a > {
2021-12-07 15:16:29 +01: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-06 17:35:20 +01:00
match self {
2021-12-07 15:16:29 +01:00
FilterCondition ::Condition { fid , .. } if depth = = 0 = > Some ( fid ) ,
FilterCondition ::Or ( left , right ) = > {
2021-12-07 17:20:11 +01:00
let depth = depth . saturating_sub ( 1 ) ;
right . token_at_depth ( depth ) . or_else ( | | left . token_at_depth ( depth ) )
2021-12-07 15:16:29 +01:00
}
FilterCondition ::And ( left , right ) = > {
2021-12-07 17:20:11 +01:00
let depth = depth . saturating_sub ( 1 ) ;
right . token_at_depth ( depth ) . or_else ( | | left . token_at_depth ( depth ) )
2021-12-07 15:16:29 +01:00
}
FilterCondition ::GeoLowerThan { point : [ point , _ ] , .. } if depth = = 0 = > Some ( point ) ,
FilterCondition ::GeoGreaterThan { point : [ point , _ ] , .. } if depth = = 0 = > Some ( point ) ,
_ = > None ,
2021-12-06 17:35:20 +01:00
}
}
2021-10-22 01:59:38 +02:00
pub fn negate ( self ) -> FilterCondition < ' a > {
use FilterCondition ::* ;
2021-10-22 01:15:42 +02:00
match self {
2021-10-22 01:59:38 +02:00
Condition { fid , op } = > match op . negate ( ) {
( op , None ) = > Condition { fid , op } ,
( a , Some ( b ) ) = > Or (
Condition { fid : fid . clone ( ) , op : a } . into ( ) ,
Condition { fid , op : b } . into ( ) ,
) ,
} ,
Or ( a , b ) = > And ( a . negate ( ) . into ( ) , b . negate ( ) . into ( ) ) ,
And ( a , b ) = > Or ( a . negate ( ) . into ( ) , b . negate ( ) . into ( ) ) ,
GeoLowerThan { point , radius } = > GeoGreaterThan { point , radius } ,
GeoGreaterThan { point , radius } = > GeoLowerThan { point , radius } ,
2021-10-22 01:15:42 +02:00
}
}
2021-12-09 11:13:12 +01:00
pub fn parse ( input : & ' a str ) -> Result < Option < Self > , Error > {
2021-10-22 17:49:08 +02:00
if input . trim ( ) . is_empty ( ) {
2021-12-09 11:13:12 +01:00
return Ok ( None ) ;
2021-10-22 17:49:08 +02:00
}
2021-11-02 17:35:17 +01:00
let span = Span ::new_extra ( input , input ) ;
2021-12-09 11:13:12 +01:00
parse_filter ( span ) . finish ( ) . map ( | ( _rem , output ) | Some ( output ) )
2021-10-22 01:59:38 +02:00
}
2021-10-22 01:15:42 +02:00
}
2021-11-09 16:40:05 +01:00
/// remove OPTIONAL whitespaces before AND after the provided parser.
2021-11-02 20:27:07 +01:00
fn ws < ' a , O > ( inner : impl FnMut ( Span < ' a > ) -> IResult < O > ) -> impl FnMut ( Span < ' a > ) -> IResult < O > {
2021-10-22 01:59:38 +02:00
delimited ( multispace0 , inner , multispace0 )
}
2021-10-22 01:15:42 +02:00
2022-06-16 09:12:37 +02:00
/// or = and ("OR" WS+ and)*
2021-11-02 20:27:07 +01:00
fn parse_or ( input : Span ) -> IResult < FilterCondition > {
2021-10-22 01:15:42 +02:00
let ( input , lhs ) = parse_and ( input ) ? ;
2021-11-04 14:22:35 +01:00
// if we found a `OR` then we MUST find something next
2022-06-16 09:12:37 +02:00
let ( input , ors ) = many0 ( preceded ( ws ( tuple ( ( tag ( " OR " ) , multispace1 ) ) ) , cut ( parse_and ) ) ) ( input ) ? ;
2021-10-22 01:15:42 +02:00
let expr = ors
. into_iter ( )
. fold ( lhs , | acc , branch | FilterCondition ::Or ( Box ::new ( acc ) , Box ::new ( branch ) ) ) ;
Ok ( ( input , expr ) )
}
2022-06-15 09:14:19 +02:00
/// and = not ("AND" not)*
2021-11-02 20:27:07 +01:00
fn parse_and ( input : Span ) -> IResult < FilterCondition > {
2021-10-22 01:15:42 +02:00
let ( input , lhs ) = parse_not ( input ) ? ;
2021-11-04 14:22:35 +01:00
// if we found a `AND` then we MUST find something next
2022-06-16 09:12:37 +02:00
let ( input , ors ) =
many0 ( preceded ( ws ( tuple ( ( tag ( " AND " ) , multispace1 ) ) ) , cut ( parse_not ) ) ) ( input ) ? ;
2021-10-22 01:15:42 +02:00
let expr = ors
. into_iter ( )
. fold ( lhs , | acc , branch | FilterCondition ::And ( Box ::new ( acc ) , Box ::new ( branch ) ) ) ;
Ok ( ( input , expr ) )
}
2022-06-16 09:12:37 +02:00
/// not = ("NOT" WS+ not) | primary
/// We can have multiple consecutive not, eg: `NOT NOT channel = mv`.
2021-11-09 16:47:54 +01:00
/// If we parse a `NOT` we MUST parse something behind.
2021-11-02 20:27:07 +01:00
fn parse_not ( input : Span ) -> IResult < FilterCondition > {
2022-06-16 09:12:37 +02:00
alt ( (
map ( preceded ( ws ( tuple ( ( tag ( " NOT " ) , multispace1 ) ) ) , cut ( parse_not ) ) , | e | e . negate ( ) ) ,
parse_primary ,
) ) ( input )
2021-10-22 01:15:42 +02:00
}
2022-06-16 06:19:33 +02:00
/// geoRadius = WS* "_geoRadius(float WS* "," WS* float WS* "," WS* float)
2021-11-04 14:22:35 +01:00
/// If we parse `_geoRadius` we MUST parse the rest of the expression.
2021-11-02 20:27:07 +01:00
fn parse_geo_radius ( input : Span ) -> IResult < FilterCondition > {
2022-06-16 09:12:37 +02:00
// we want to allow space BEFORE the _geoRadius but not after
2021-11-04 14:22:35 +01:00
let parsed = preceded (
2021-10-22 14:33:18 +02:00
tuple ( ( multispace0 , tag ( " _geoRadius " ) ) ) ,
2021-11-09 16:40:05 +01:00
// if we were able to parse `_geoRadius` and can't parse the rest of the input we return a failure
2021-11-09 01:03:02 +01:00
cut ( delimited ( char ( '(' ) , separated_list1 ( tag ( " , " ) , ws ( recognize_float ) ) , char ( ')' ) ) ) ,
2021-11-04 14:22:35 +01:00
) ( input )
2021-11-04 16:20:53 +01:00
. map_err ( | e | e . map ( | _ | Error ::new_from_kind ( input , ErrorKind ::Geo ) ) ) ;
2021-10-22 01:15:42 +02:00
2021-11-04 14:22:35 +01:00
let ( input , args ) = parsed ? ;
2021-10-22 01:15:42 +02:00
if args . len ( ) ! = 3 {
2021-11-04 16:20:53 +01:00
return Err ( nom ::Err ::Failure ( Error ::new_from_kind ( input , ErrorKind ::Geo ) ) ) ;
2021-10-22 01:15:42 +02:00
}
let res = FilterCondition ::GeoLowerThan {
point : [ args [ 0 ] . into ( ) , args [ 1 ] . into ( ) ] ,
radius : args [ 2 ] . into ( ) ,
} ;
Ok ( ( input , res ) )
}
2022-06-16 06:19:33 +02:00
/// geoPoint = WS* "_geoPoint(float WS* "," WS* float WS* "," WS* float)
2021-11-04 14:22:35 +01: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 15:59:04 +01:00
cut ( delimited ( char ( '(' ) , separated_list1 ( tag ( " , " ) , ws ( recognize_float ) ) , char ( ')' ) ) ) ,
2021-11-04 14:22:35 +01:00
) ) ( input )
2021-11-04 16:20:53 +01:00
. map_err ( | e | e . map ( | _ | Error ::new_from_kind ( input , ErrorKind ::ReservedGeo ( " _geoPoint " ) ) ) ) ? ;
2021-11-09 16:40:05 +01:00
// if we succeeded we still return a `Failure` because geoPoints are not allowed
2021-11-04 16:20:53 +01:00
Err ( nom ::Err ::Failure ( Error ::new_from_kind ( input , ErrorKind ::ReservedGeo ( " _geoPoint " ) ) ) )
2021-11-04 14:22:35 +01:00
}
2022-06-16 09:12:37 +02:00
/// primary = (WS* "(" WS* expression WS* ")" WS*) | geoRadius | condition | exists | not_exists | to
2021-11-02 20:27:07 +01:00
fn parse_primary ( input : Span ) -> IResult < FilterCondition > {
2021-10-22 01:15:42 +02:00
alt ( (
2021-11-04 14:22:35 +01: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 16:20:53 +01:00
Error ::new_from_kind ( input , ErrorKind ::MissingClosingDelimiter ( c . char ( ) ) )
2021-11-04 14:22:35 +01:00
} ) ,
) ,
2021-11-09 01:03:02 +01:00
parse_geo_radius ,
parse_condition ,
2022-06-14 16:42:09 +02:00
parse_exists ,
parse_not_exists ,
2021-11-09 01:03:02 +01:00
parse_to ,
2021-11-04 14:22:35 +01: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 01:03:02 +01:00
parse_geo_point ,
2021-10-22 01:15:42 +02:00
) ) ( input )
2021-11-04 14:22:35 +01:00
// if the inner parsers did not match enough information to return an accurate error
2021-11-04 16:20:53 +01:00
. map_err ( | e | e . map_err ( | _ | Error ::new_from_kind ( input , ErrorKind ::InvalidPrimary ) ) )
2021-10-22 01:15:42 +02:00
}
/// expression = or
2021-11-02 20:27:07 +01:00
pub fn parse_expression ( input : Span ) -> IResult < FilterCondition > {
2021-10-22 01:15:42 +02:00
parse_or ( input )
}
2022-06-15 09:14:19 +02:00
/// filter = expression EOF
2021-11-02 20:27:07 +01:00
pub fn parse_filter ( input : Span ) -> IResult < FilterCondition > {
2021-11-02 17:35:17 +01:00
terminated ( parse_expression , eof ) ( input )
}
2021-10-22 01:15:42 +02:00
#[ cfg(test) ]
2021-10-22 01:59:38 +02:00
pub mod tests {
2021-10-22 01:15:42 +02:00
use super ::* ;
/// Create a raw [Token]. You must specify the string that appear BEFORE your element followed by your element
2021-10-22 01:59:38 +02:00
pub fn rtok < ' a > ( before : & ' a str , value : & ' a str ) -> Token < ' a > {
2021-10-22 01:15:42 +02: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-02 17:35:17 +01: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 01:15:42 +02:00
}
#[ test ]
fn parse ( ) {
use FilterCondition as Fc ;
let test_case = [
// simple test
(
" channel = Ponce " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " channel " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Equal ( rtok ( " channel = " , " Ponce " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" subscribers = 12 " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " subscribers " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Equal ( rtok ( " subscribers = " , " 12 " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
// test all the quotes and simple quotes
(
" channel = 'Mister Mv' " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " channel " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Equal ( rtok ( " channel = ' " , " Mister Mv " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" channel = \" Mister Mv \" " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " channel " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Equal ( rtok ( " channel = \" " , " Mister Mv " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" 'dog race' = Borzoi " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " ' " , " dog race " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Equal ( rtok ( " 'dog race' = " , " Borzoi " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" \" dog race \" = Chusky " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " \" " , " dog race " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Equal ( rtok ( " \" dog race \" = " , " Chusky " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" \" dog race \" = \" Bernese Mountain \" " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " \" " , " dog race " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Equal ( rtok ( " \" dog race \" = \" " , " Bernese Mountain " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" 'dog race' = 'Bernese Mountain' " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " ' " , " dog race " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Equal ( rtok ( " 'dog race' = ' " , " Bernese Mountain " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" \" dog race \" = 'Bernese Mountain' " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " \" " , " dog race " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Equal ( rtok ( " \" dog race \" = \" " , " Bernese Mountain " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
// test all the operators
(
" channel != ponce " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " channel " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::NotEqual ( rtok ( " channel != " , " ponce " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" NOT channel = ponce " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " NOT " , " channel " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::NotEqual ( rtok ( " NOT channel = " , " ponce " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" subscribers < 1000 " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " subscribers " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::LowerThan ( rtok ( " subscribers < " , " 1000 " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" subscribers > 1000 " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " subscribers " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::GreaterThan ( rtok ( " subscribers > " , " 1000 " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" subscribers <= 1000 " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " subscribers " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::LowerThanOrEqual ( rtok ( " subscribers <= " , " 1000 " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" subscribers >= 1000 " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " subscribers " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::GreaterThanOrEqual ( rtok ( " subscribers >= " , " 1000 " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" NOT subscribers < 1000 " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " NOT " , " subscribers " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::GreaterThanOrEqual ( rtok ( " NOT subscribers < " , " 1000 " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" NOT subscribers > 1000 " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " NOT " , " subscribers " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::LowerThanOrEqual ( rtok ( " NOT subscribers > " , " 1000 " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" NOT subscribers <= 1000 " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " NOT " , " subscribers " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::GreaterThan ( rtok ( " NOT subscribers <= " , " 1000 " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
(
" NOT subscribers >= 1000 " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " NOT " , " subscribers " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::LowerThan ( rtok ( " NOT subscribers >= " , " 1000 " ) ) ,
2021-10-22 01:15:42 +02:00
} ,
) ,
2022-05-25 11:55:16 +02:00
(
2022-06-14 16:42:09 +02:00
" subscribers EXISTS " ,
2022-05-25 11:55:16 +02:00
Fc ::Condition {
fid : rtok ( " " , " subscribers " ) ,
2022-06-14 16:42:09 +02:00
op : Condition ::Exists ,
2022-05-25 11:55:16 +02:00
} ,
) ,
(
2022-06-14 16:42:09 +02:00
" NOT subscribers EXISTS " ,
2022-05-25 11:55:16 +02:00
Fc ::Condition {
fid : rtok ( " NOT " , " subscribers " ) ,
2022-06-14 16:42:09 +02:00
op : Condition ::NotExists ,
} ,
) ,
(
" subscribers NOT EXISTS " ,
Fc ::Condition {
fid : rtok ( " " , " subscribers " ) ,
op : Condition ::NotExists ,
2022-05-25 11:55:16 +02:00
} ,
) ,
2022-06-15 09:14:19 +02:00
(
" NOT subscribers NOT EXISTS " ,
Fc ::Condition {
fid : rtok ( " NOT " , " subscribers " ) ,
op : Condition ::Exists ,
} ,
) ,
(
" subscribers NOT EXISTS " ,
Fc ::Condition {
fid : rtok ( " " , " subscribers " ) ,
op : Condition ::NotExists ,
} ,
) ,
2021-10-22 01:15:42 +02:00
(
" subscribers 100 TO 1000 " ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " subscribers " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Between {
2021-10-22 01:15:42 +02:00
from : rtok ( " subscribers " , " 100 " ) ,
to : rtok ( " subscribers 100 TO " , " 1000 " ) ,
} ,
} ,
) ,
(
" NOT subscribers 100 TO 1000 " ,
Fc ::Or (
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " NOT " , " subscribers " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::LowerThan ( rtok ( " NOT subscribers " , " 100 " ) ) ,
2021-10-22 01:15:42 +02:00
}
. into ( ) ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " NOT " , " subscribers " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::GreaterThan ( rtok ( " NOT subscribers 100 TO " , " 1000 " ) ) ,
2021-10-22 01:15:42 +02:00
}
. into ( ) ,
) ,
) ,
(
" _geoRadius(12, 13, 14) " ,
Fc ::GeoLowerThan {
point : [ rtok ( " _geoRadius( " , " 12 " ) , rtok ( " _geoRadius(12, " , " 13 " ) ] ,
radius : rtok ( " _geoRadius(12, 13, " , " 14 " ) ,
} ,
) ,
(
" NOT _geoRadius(12, 13, 14) " ,
Fc ::GeoGreaterThan {
point : [ rtok ( " NOT _geoRadius( " , " 12 " ) , rtok ( " NOT _geoRadius(12, " , " 13 " ) ] ,
radius : rtok ( " NOT _geoRadius(12, 13, " , " 14 " ) ,
} ,
) ,
// test simple `or` and `and`
(
" channel = ponce AND 'dog race' != 'bernese mountain' " ,
Fc ::And (
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " channel " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Equal ( rtok ( " channel = " , " ponce " ) ) ,
2021-10-22 01:15:42 +02:00
}
. into ( ) ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " channel = ponce AND ' " , " dog race " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::NotEqual ( rtok (
2021-10-22 01:15:42 +02:00
" channel = ponce AND 'dog race' != ' " ,
" bernese mountain " ,
) ) ,
}
. into ( ) ,
) ,
) ,
(
" channel = ponce OR 'dog race' != 'bernese mountain' " ,
Fc ::Or (
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " channel " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Equal ( rtok ( " channel = " , " ponce " ) ) ,
2021-10-22 01:15:42 +02:00
}
. into ( ) ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " channel = ponce OR ' " , " dog race " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::NotEqual ( rtok (
2021-10-22 01:15:42 +02:00
" channel = ponce OR 'dog race' != ' " ,
" bernese mountain " ,
) ) ,
}
. into ( ) ,
) ,
) ,
(
" channel = ponce AND 'dog race' != 'bernese mountain' OR subscribers > 1000 " ,
Fc ::Or (
Fc ::And (
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " " , " channel " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::Equal ( rtok ( " channel = " , " ponce " ) ) ,
2021-10-22 01:15:42 +02:00
}
. into ( ) ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok ( " channel = ponce AND ' " , " dog race " ) ,
2021-10-22 01:59:38 +02:00
op : Condition ::NotEqual ( rtok (
2021-10-22 01:15:42 +02:00
" channel = ponce AND 'dog race' != ' " ,
" bernese mountain " ,
) ) ,
}
. into ( ) ,
)
. into ( ) ,
2021-10-22 01:59:38 +02:00
Fc ::Condition {
2021-10-22 01:15:42 +02:00
fid : rtok (
" channel = ponce AND 'dog race' != 'bernese mountain' OR " ,
" subscribers " ,
) ,
2021-10-22 01:59:38 +02:00
op : Condition ::GreaterThan ( rtok (
2021-10-22 01:15:42 +02:00
" channel = ponce AND 'dog race' != 'bernese mountain' OR subscribers > " ,
" 1000 " ,
) ) ,
}
. into ( ) ,
) ,
) ,
// test parenthesis
(
" channel = ponce AND ( 'dog race' != 'bernese mountain' OR subscribers > 1000 ) " ,
Fc ::And (
2021-10-22 01:59:38 +02:00
Fc ::Condition { fid : rtok ( " " , " channel " ) , op : Condition ::Equal ( rtok ( " channel = " , " ponce " ) ) } . into ( ) ,
2021-10-22 01:15:42 +02:00
Fc ::Or (
2021-10-22 01:59:38 +02:00
Fc ::Condition { fid : rtok ( " channel = ponce AND ( ' " , " dog race " ) , op : Condition ::NotEqual ( rtok ( " channel = ponce AND ( 'dog race' != ' " , " bernese mountain " ) ) } . into ( ) ,
Fc ::Condition { fid : rtok ( " channel = ponce AND ( 'dog race' != 'bernese mountain' OR " , " subscribers " ) , op : Condition ::GreaterThan ( rtok ( " channel = ponce AND ( 'dog race' != 'bernese mountain' OR subscribers > " , " 1000 " ) ) } . into ( ) ,
2021-10-22 01:15:42 +02:00
) . into ( ) ) ,
) ,
(
" (channel = ponce AND 'dog race' != 'bernese mountain' OR subscribers > 1000) AND _geoRadius(12, 13, 14) " ,
Fc ::And (
Fc ::Or (
Fc ::And (
2021-10-22 01:59:38 +02:00
Fc ::Condition { fid : rtok ( " ( " , " channel " ) , op : Condition ::Equal ( rtok ( " (channel = " , " ponce " ) ) } . into ( ) ,
Fc ::Condition { fid : rtok ( " (channel = ponce AND ' " , " dog race " ) , op : Condition ::NotEqual ( rtok ( " (channel = ponce AND 'dog race' != ' " , " bernese mountain " ) ) } . into ( ) ,
2021-10-22 01:15:42 +02:00
) . into ( ) ,
2021-10-22 01:59:38 +02:00
Fc ::Condition { fid : rtok ( " (channel = ponce AND 'dog race' != 'bernese mountain' OR " , " subscribers " ) , op : Condition ::GreaterThan ( rtok ( " (channel = ponce AND 'dog race' != 'bernese mountain' OR subscribers > " , " 1000 " ) ) } . into ( ) ,
2021-10-22 01:15:42 +02:00
) . into ( ) ,
Fc ::GeoLowerThan { point : [ rtok ( " (channel = ponce AND 'dog race' != 'bernese mountain' OR subscribers > 1000) AND _geoRadius( " , " 12 " ) , rtok ( " (channel = ponce AND 'dog race' != 'bernese mountain' OR subscribers > 1000) AND _geoRadius(12, " , " 13 " ) ] , radius : rtok ( " (channel = ponce AND 'dog race' != 'bernese mountain' OR subscribers > 1000) AND _geoRadius(12, 13, " , " 14 " ) } . into ( )
)
)
] ;
for ( input , expected ) in test_case {
2021-11-02 20:27:07 +01:00
let result = Fc ::parse ( input ) ;
2021-10-22 01:15:42 +02:00
assert! (
result . is_ok ( ) ,
" Filter `{:?}` was supposed to be parsed but failed with the following error: `{}` " ,
expected ,
result . unwrap_err ( )
) ;
2021-10-22 14:33:18 +02:00
let filter = result . unwrap ( ) ;
2021-12-09 11:13:12 +01:00
assert_eq! ( filter , Some ( expected ) , " Filter `{}` failed. " , input ) ;
2021-10-22 01:15:42 +02:00
}
}
2021-11-02 17:35:17 +01:00
#[ test ]
fn error ( ) {
use FilterCondition as Fc ;
let test_case = [
// simple test
2021-11-04 14:22:35 +01:00
( " channel = Ponce = 12 " , " Found unexpected characters at the end of the filter: `= 12`. You probably forgot an `OR` or an `AND` rule. " ) ,
( " channel = " , " Was expecting a value but instead got nothing. " ) ,
( " channel = 🐻 " , " Was expecting a value but instead got `🐻`. " ) ,
2021-11-08 15:30:26 +01:00
( " channel = 🐻 AND followers < 100 " , " Was expecting a value but instead got `🐻`. " ) ,
2022-06-14 16:42:09 +02:00
( " OR " , " Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `TO`, `EXISTS`, `NOT EXISTS`, or `_geoRadius` at `OR`. " ) ,
( " AND " , " Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `TO`, `EXISTS`, `NOT EXISTS`, or `_geoRadius` at `AND`. " ) ,
( " channel Ponce " , " Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `TO`, `EXISTS`, `NOT EXISTS`, or `_geoRadius` at `channel Ponce`. " ) ,
2022-06-16 09:12:37 +02:00
( " channel = Ponce OR " , " Found unexpected characters at the end of the filter: `OR`. You probably forgot an `OR` or an `AND` rule. " ) ,
2021-11-04 14:22:35 +01:00
( " _geoRadius " , " The `_geoRadius` filter expects three arguments: `_geoRadius(latitude, longitude, radius)`. " ) ,
( " _geoRadius = 12 " , " The `_geoRadius` filter expects three arguments: `_geoRadius(latitude, longitude, radius)`. " ) ,
( " _geoPoint(12, 13, 14) " , " `_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. " ) ,
( " position <= _geoPoint(12, 13, 14) " , " `_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. " ) ,
( " position <= _geoRadius(12, 13, 14) " , " The `_geoRadius` filter is an operation and can't be used as a value. " ) ,
2021-11-09 16:25:53 +01:00
( " channel = 'ponce " , " Expression ` \\ 'ponce` is missing the following closing delimiter: `'`. " ) ,
( " channel = \" ponce " , " Expression ` \\ \" ponce` is missing the following closing delimiter: ` \" `. " ) ,
2021-11-04 16:03:52 +01:00
( " channel = mv OR (followers >= 1000 " , " Expression `(followers >= 1000` is missing the following closing delimiter: `)`. " ) ,
2021-11-04 14:22:35 +01:00
( " channel = mv OR followers >= 1000) " , " Found unexpected characters at the end of the filter: `)`. You probably forgot an `OR` or an `AND` rule. " ) ,
2022-06-15 09:14:19 +02:00
( " colour NOT EXIST " , " Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `TO`, `EXISTS`, `NOT EXISTS`, or `_geoRadius` at `colour NOT EXIST`. " ) ,
2022-06-16 09:12:37 +02:00
( " subscribers 100 TO1000 " , " Was expecting an operation `=`, `!=`, `>=`, `>`, `<=`, `<`, `TO`, `EXISTS`, `NOT EXISTS`, or `_geoRadius` at `subscribers 100 TO1000`. " ) ,
( " channel = ponce ORdog != 'bernese mountain' " , " Found unexpected characters at the end of the filter: `ORdog != \\ 'bernese mountain \\ '`. You probably forgot an `OR` or an `AND` rule. " ) ,
( " channel = ponce AND'dog' != 'bernese mountain' " , " Found unexpected characters at the end of the filter: `AND \\ 'dog \\ ' != \\ 'bernese mountain \\ '`. You probably forgot an `OR` or an `AND` rule. " ) ,
2021-11-02 17:35:17 +01:00
] ;
for ( input , expected ) in test_case {
2021-11-02 20:27:07 +01:00
let result = Fc ::parse ( input ) ;
2021-11-02 17:35:17 +01:00
assert! (
result . is_err ( ) ,
2021-11-04 14:22:35 +01:00
" Filter `{}` wasn't supposed to be parsed but it did with the following result: `{:?}` " ,
input ,
2021-11-02 17:35:17 +01:00
result . unwrap ( )
) ;
let filter = result . unwrap_err ( ) . to_string ( ) ;
2021-11-04 14:22:35 +01:00
assert! ( filter . starts_with ( expected ) , " Filter `{:?}` was supposed to return the following error: \n {} \n , but instead returned \n {} \n . " , input , expected , filter ) ;
2021-11-02 17:35:17 +01:00
}
}
2021-12-06 17:35:20 +01:00
#[ test ]
fn depth ( ) {
2021-12-09 11:13:12 +01: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 ( ) ;
2021-12-07 15:16:29 +01:00
assert! ( filter . token_at_depth ( 5 ) . is_some ( ) ) ;
2021-12-06 17:35:20 +01:00
}
2021-10-22 01:15:42 +02:00
}