2021-10-22 01:15:42 +02:00
|
|
|
|
//! BNF grammar:
|
|
|
|
|
//!
|
|
|
|
|
//! ```text
|
2021-11-02 17:35:17 +01:00
|
|
|
|
//! filter = expression ~ EOF
|
2021-10-22 01:15:42 +02:00
|
|
|
|
//! expression = or
|
|
|
|
|
//! or = and (~ "OR" ~ and)
|
|
|
|
|
//! and = not (~ "AND" not)*
|
|
|
|
|
//! not = ("NOT" | "!") not | primary
|
2021-11-02 17:35:17 +01:00
|
|
|
|
//! primary = (WS* ~ "(" expression ")" ~ WS*) | geoRadius | condition | to
|
2021-10-22 01:15:42 +02:00
|
|
|
|
//! condition = value ("==" | ">" ...) value
|
2021-10-22 01:59:38 +02:00
|
|
|
|
//! to = value value TO value
|
2021-10-22 01:15:42 +02:00
|
|
|
|
//! value = WS* ~ ( word | singleQuoted | doubleQuoted) ~ WS*
|
|
|
|
|
//! singleQuoted = "'" .* all but quotes "'"
|
|
|
|
|
//! doubleQuoted = "\"" (word | spaces)* "\""
|
|
|
|
|
//! word = (alphanumeric | _ | - | .)+
|
2021-11-02 17:35:17 +01:00
|
|
|
|
//! geoRadius = WS* ~ "_geoRadius(" ~ float ~ "," ~ float ~ "," float ~ ")"
|
|
|
|
|
//! ```
|
|
|
|
|
//!
|
|
|
|
|
//! Other BNF grammar used to handle some specific errors:
|
|
|
|
|
//! ```text
|
|
|
|
|
//! geoPoint = WS* ~ "_geoPoint(" ~ (float ~ ",")* ~ ")"
|
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-10-22 01:59:38 +02:00
|
|
|
|
pub use condition::{parse_condition, parse_to, Condition};
|
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;
|
2021-10-22 01:15:42 +02:00
|
|
|
|
use nom::character::complete::{char, multispace0};
|
2021-11-02 17:35:17 +01:00
|
|
|
|
use nom::combinator::{cut, eof, map};
|
|
|
|
|
use nom::error::{ContextError, ParseError};
|
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-10-22 15:09:56 +02:00
|
|
|
|
use nom::{Finish, IResult};
|
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 17:35:17 +01:00
|
|
|
|
pub trait FilterParserError<'a>: ParseError<Span<'a>> + ContextError<Span<'a>> {}
|
|
|
|
|
impl<'a, T> FilterParserError<'a> for T where T: ParseError<Span<'a>> + ContextError<Span<'a>> {}
|
2021-10-22 15:09:56 +02:00
|
|
|
|
|
|
|
|
|
use FilterParserError as FPError;
|
|
|
|
|
|
2021-10-22 01:15:42 +02:00
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
|
pub struct Token<'a> {
|
|
|
|
|
pub position: Span<'a>,
|
|
|
|
|
pub inner: &'a str,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> Token<'a> {
|
|
|
|
|
pub fn new(position: Span<'a>) -> Self {
|
|
|
|
|
Self { position, inner: &position }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<'a> From<Span<'a>> for Token<'a> {
|
|
|
|
|
fn from(span: Span<'a>) -> Self {
|
|
|
|
|
Self { inner: &span, position: span }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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> },
|
|
|
|
|
Empty,
|
2021-10-22 01:15:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-22 01:59:38 +02:00
|
|
|
|
impl<'a> FilterCondition<'a> {
|
|
|
|
|
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()),
|
|
|
|
|
Empty => Empty,
|
|
|
|
|
GeoLowerThan { point, radius } => GeoGreaterThan { point, radius },
|
|
|
|
|
GeoGreaterThan { point, radius } => GeoLowerThan { point, radius },
|
2021-10-22 01:15:42 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-22 15:09:56 +02:00
|
|
|
|
pub fn parse<E: FPError<'a>>(input: &'a str) -> Result<Self, E> {
|
2021-10-22 17:49:08 +02:00
|
|
|
|
if input.trim().is_empty() {
|
|
|
|
|
return Ok(Self::Empty);
|
|
|
|
|
}
|
2021-11-02 17:35:17 +01:00
|
|
|
|
let span = Span::new_extra(input, input);
|
|
|
|
|
parse_filter::<'a, E>(span).finish().map(|(_rem, output)| output)
|
2021-10-22 01:59:38 +02:00
|
|
|
|
}
|
2021-10-22 01:15:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-22 01:59:38 +02:00
|
|
|
|
// remove OPTIONAL whitespaces before AND after the the provided parser
|
2021-10-22 15:09:56 +02:00
|
|
|
|
fn ws<'a, O, E: FPError<'a>>(
|
2021-10-22 14:33:18 +02:00
|
|
|
|
inner: impl FnMut(Span<'a>) -> IResult<Span, O, E>,
|
|
|
|
|
) -> impl FnMut(Span<'a>) -> IResult<Span, O, E> {
|
2021-10-22 01:59:38 +02:00
|
|
|
|
delimited(multispace0, inner, multispace0)
|
|
|
|
|
}
|
2021-10-22 01:15:42 +02:00
|
|
|
|
|
|
|
|
|
/// and = not (~ "AND" not)*
|
2021-10-22 15:09:56 +02:00
|
|
|
|
fn parse_or<'a, E: FPError<'a>>(input: Span<'a>) -> IResult<Span, FilterCondition, E> {
|
2021-10-22 01:15:42 +02:00
|
|
|
|
let (input, lhs) = parse_and(input)?;
|
2021-11-02 17:35:17 +01:00
|
|
|
|
let (input, ors) = many0(preceded(ws(tag("OR")), 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))
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-22 15:09:56 +02:00
|
|
|
|
fn parse_and<'a, E: FPError<'a>>(input: Span<'a>) -> IResult<Span, FilterCondition, E> {
|
2021-10-22 01:15:42 +02:00
|
|
|
|
let (input, lhs) = parse_not(input)?;
|
2021-11-02 17:35:17 +01:00
|
|
|
|
let (input, ors) = many0(preceded(ws(tag("AND")), 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))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// not = ("NOT" | "!") not | primary
|
2021-10-22 15:09:56 +02:00
|
|
|
|
fn parse_not<'a, E: FPError<'a>>(input: Span<'a>) -> IResult<Span, FilterCondition, E> {
|
2021-11-02 17:35:17 +01:00
|
|
|
|
alt((
|
|
|
|
|
map(preceded(alt((tag("!"), tag("NOT"))), cut(parse_not)), |e| e.negate()),
|
|
|
|
|
cut(parse_primary),
|
|
|
|
|
))(input)
|
2021-10-22 01:15:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// geoRadius = WS* ~ "_geoRadius(float ~ "," ~ float ~ "," float)
|
2021-10-22 15:09:56 +02:00
|
|
|
|
fn parse_geo_radius<'a, E: FPError<'a>>(input: Span<'a>) -> IResult<Span<'a>, FilterCondition, E> {
|
|
|
|
|
let err_msg_args_incomplete = "_geoRadius. The `_geoRadius` filter expect three arguments: `_geoRadius(latitude, longitude, radius)`";
|
2021-10-22 01:15:42 +02:00
|
|
|
|
|
2021-10-22 14:33:18 +02:00
|
|
|
|
// we want to forbid space BEFORE the _geoRadius but not after
|
2021-10-22 01:15:42 +02:00
|
|
|
|
let parsed = preceded::<_, _, _, _, _, _>(
|
2021-10-22 14:33:18 +02:00
|
|
|
|
tuple((multispace0, tag("_geoRadius"))),
|
2021-11-02 17:35:17 +01:00
|
|
|
|
cut(delimited(char('('), separated_list1(tag(","), ws(|c| recognize_float(c))), char(')'))),
|
2021-10-22 01:15:42 +02:00
|
|
|
|
)(input);
|
|
|
|
|
|
2021-10-22 14:33:18 +02:00
|
|
|
|
let (input, args): (Span, Vec<Span>) = parsed?;
|
2021-10-22 01:15:42 +02:00
|
|
|
|
|
|
|
|
|
if args.len() != 3 {
|
2021-10-22 15:09:56 +02:00
|
|
|
|
let e = E::from_char(input, '(');
|
|
|
|
|
return Err(nom::Err::Failure(E::add_context(input, err_msg_args_incomplete, e)));
|
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))
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-02 17:35:17 +01:00
|
|
|
|
/// primary = (WS* ~ "(" expression ")" ~ WS*) | geoRadius | condition | to
|
2021-10-22 15:09:56 +02:00
|
|
|
|
fn parse_primary<'a, E: FPError<'a>>(input: Span<'a>) -> IResult<Span, FilterCondition, E> {
|
2021-10-22 01:15:42 +02:00
|
|
|
|
alt((
|
2021-11-02 17:35:17 +01:00
|
|
|
|
delimited(ws(char('(')), cut(parse_expression), cut(ws(char(')')))),
|
|
|
|
|
|c| parse_geo_radius(c),
|
2021-10-22 01:15:42 +02:00
|
|
|
|
|c| parse_condition(c),
|
|
|
|
|
|c| parse_to(c),
|
|
|
|
|
))(input)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// expression = or
|
2021-10-22 15:09:56 +02:00
|
|
|
|
pub fn parse_expression<'a, E: FPError<'a>>(input: Span<'a>) -> IResult<Span, FilterCondition, E> {
|
2021-10-22 01:15:42 +02:00
|
|
|
|
parse_or(input)
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-02 17:35:17 +01:00
|
|
|
|
/// filter = expression ~ EOF
|
|
|
|
|
pub fn parse_filter<'a, E: FPError<'a>>(input: Span<'a>) -> IResult<Span, FilterCondition, E> {
|
|
|
|
|
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
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"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-10-22 14:33:18 +02:00
|
|
|
|
let result = Fc::parse::<Error<Span>>(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-10-22 01:15:42 +02:00
|
|
|
|
assert_eq!(filter, expected, "Filter `{}` failed.", input);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-11-02 17:35:17 +01:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn error() {
|
|
|
|
|
use FilterCondition as Fc;
|
|
|
|
|
|
|
|
|
|
let result = Fc::parse::<crate::Error<Span>>("test = truc OR truc");
|
|
|
|
|
assert!(result.is_err());
|
|
|
|
|
|
|
|
|
|
let test_case = [
|
|
|
|
|
// simple test
|
|
|
|
|
("OR", "An error occured"),
|
|
|
|
|
("AND", "An error occured"),
|
|
|
|
|
("channel = Ponce OR", "An error occured"),
|
|
|
|
|
("channel = Ponce = 12", "An error occured"),
|
|
|
|
|
("_geoRadius = 12", "An error occured"),
|
|
|
|
|
("_geoPoint(12, 13, 14)", "An error occured"),
|
|
|
|
|
("_geo = _geoRadius(12, 13, 14)", "An error occured"),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (input, expected) in test_case {
|
|
|
|
|
let result = Fc::parse::<Error<Span>>(input);
|
|
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
|
result.is_err(),
|
|
|
|
|
"Filter `{:?}` wasn't supposed to be parsed but it did with the following result: `{:?}`",
|
|
|
|
|
expected,
|
|
|
|
|
result.unwrap()
|
|
|
|
|
);
|
|
|
|
|
let filter = result.unwrap_err().to_string();
|
|
|
|
|
assert_eq!(filter, expected, "Filter `{:?}` was supposed to return the following error: `{}`, but instead returned `{}`.", input, filter, expected);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
#[test]
|
|
|
|
|
fn bidule() {
|
|
|
|
|
use FilterCondition as Fc;
|
|
|
|
|
|
|
|
|
|
let result = Fc::parse::<crate::Error<Span>>("test = truc OR truc");
|
|
|
|
|
dbg!(result);
|
|
|
|
|
|
|
|
|
|
assert!(false);
|
|
|
|
|
}
|
|
|
|
|
*/
|
2021-10-22 01:15:42 +02:00
|
|
|
|
}
|