use proc_macro2::Ident;
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::{Lit, Token, parenthesized, token::Paren};
#[derive(Debug, PartialEq, Clone)]
pub struct FilterExpression {
pub _parens: Paren,
pub expr: FilterExpressionInner,
}
#[derive(Debug, PartialEq, Clone)]
pub enum FilterExpressionInner {
All(Punctuated<FilterExpressionInner, Token![&&]>),
Any(Punctuated<FilterExpressionInner, Token![||]>),
Comparison(PredicateComparison),
}
impl Default for FilterExpression {
fn default() -> Self {
Self {
_parens: Default::default(),
expr: FilterExpressionInner::All(Punctuated::new()),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PredicateComparison {
pub(crate) field: Ident,
operator: WhereOp,
value: Lit,
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub enum WhereOp {
Equal(Token![==]),
NotEqual(Token![!=]),
}
impl Parse for FilterExpression {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
let _parens = parenthesized!(content in input);
let expr = content.parse()?;
Ok(Self { _parens, expr })
}
}
impl Parse for FilterExpressionInner {
fn parse(input: ParseStream) -> syn::Result<Self> {
let first = FilterExpressionInner::Comparison(input.parse()?);
if input.peek(Token![&&]) {
let mut all = syn::punctuated::Punctuated::new();
all.push(first);
all.push_punct(input.parse()?);
all.extend(syn::punctuated::Punctuated::parse_terminated(input)?.into_pairs());
Ok(FilterExpressionInner::All(all))
} else if input.peek(Token![||]) {
let mut any = syn::punctuated::Punctuated::new();
any.push(first);
any.push_punct(input.parse()?);
any.extend(syn::punctuated::Punctuated::parse_terminated(input)?.into_pairs());
Ok(FilterExpressionInner::Any(any))
} else {
Ok(first)
}
}
}
impl Parse for PredicateComparison {
fn parse(input: ParseStream) -> syn::Result<Self> {
let field;
let operator;
let value;
if input.peek(syn::Ident) {
field = input.parse()?;
operator = input.parse()?;
value = input.parse()?;
} else if input.peek(syn::Lit) {
value = input.parse()?;
operator = input.parse()?;
field = input.parse()?;
} else {
return Err(input.error("Expected ident or literal"));
}
let comparison = PredicateComparison {
field,
operator,
value,
};
Ok(comparison)
}
}
impl Parse for WhereOp {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
if input.peek(Token![==]) {
input.parse().map(Self::Equal)
} else if input.peek(Token![!=]) {
input.parse().map(Self::NotEqual)
} else {
Err(input.error("Expected `==` or `!=`, other comparators are not yet supported."))
}
}
}
impl PredicateComparison {
pub fn evaluate(&self, record_value: Lit) -> bool {
match self.operator {
WhereOp::Equal(_) => record_value == self.value,
WhereOp::NotEqual(_) => record_value != self.value,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_debug_snapshot;
use syn::parse_quote;
#[test]
fn parse_condition() {
let parsed1: FilterExpressionInner = parse_quote!(field == "field");
let parsed2: FilterExpressionInner = parse_quote!("field" == field);
assert_eq!(parsed1, parsed2);
assert_debug_snapshot!(parsed1, @r###"
Comparison(
PredicateComparison {
field: Ident(
field,
),
operator: Equal(
EqEq,
),
value: Lit::Str {
token: "field",
},
},
)
"###);
let parsed1: FilterExpressionInner = parse_quote!(field != "field");
let parsed2: FilterExpressionInner = parse_quote!("field" != field);
assert_eq!(parsed1, parsed2);
assert_debug_snapshot!(parsed1, @r###"
Comparison(
PredicateComparison {
field: Ident(
field,
),
operator: NotEqual(
Ne,
),
value: Lit::Str {
token: "field",
},
},
)
"###);
let parsed1: FilterExpressionInner =
parse_quote!(field == "field" && field2 == "field2" && field3 == "field3");
assert_debug_snapshot!(parsed1, @r###"
All(
[
Comparison(
PredicateComparison {
field: Ident(
field,
),
operator: Equal(
EqEq,
),
value: Lit::Str {
token: "field",
},
},
),
AndAnd,
All(
[
Comparison(
PredicateComparison {
field: Ident(
field2,
),
operator: Equal(
EqEq,
),
value: Lit::Str {
token: "field2",
},
},
),
AndAnd,
Comparison(
PredicateComparison {
field: Ident(
field3,
),
operator: Equal(
EqEq,
),
value: Lit::Str {
token: "field3",
},
},
),
],
),
],
)
"###);
let parsed1: FilterExpressionInner = parse_quote!(field == "field" || field2 == "field2");
assert_debug_snapshot!(parsed1, @r###"
Any(
[
Comparison(
PredicateComparison {
field: Ident(
field,
),
operator: Equal(
EqEq,
),
value: Lit::Str {
token: "field",
},
},
),
OrOr,
Comparison(
PredicateComparison {
field: Ident(
field2,
),
operator: Equal(
EqEq,
),
value: Lit::Str {
token: "field2",
},
},
),
],
)
"###);
}
}