Skip to main content

mool_macros_impl/
filterable.rs

1use darling::FromMeta;
2use quote::quote;
3use syn::{
4    DeriveInput, Error, GenericArgument, Ident, Path, PathArguments, Type, spanned::Spanned,
5};
6
7#[derive(Clone, Copy, PartialEq, Eq)]
8enum FilterOp {
9    Eq,
10    Ne,
11    Lt,
12    Lte,
13    Gt,
14    Gte,
15    Like,
16    ILike,
17    In,
18}
19
20impl FilterOp {
21    fn method(self) -> Ident {
22        Ident::new(
23            match self {
24                FilterOp::Eq => "eq",
25                FilterOp::Ne => "ne",
26                FilterOp::Lt => "lt",
27                FilterOp::Lte => "lte",
28                FilterOp::Gt => "gt",
29                FilterOp::Gte => "gte",
30                FilterOp::Like => "like",
31                FilterOp::ILike => "ilike",
32                FilterOp::In => "in",
33            },
34            proc_macro2::Span::call_site(),
35        )
36    }
37}
38
39struct FilterAttr {
40    op: FilterOp,
41    column: Option<String>,
42    span: proc_macro2::Span,
43}
44
45#[derive(Default, FromMeta)]
46struct FilterContainer {
47    model: Option<Path>,
48}
49
50#[derive(Default, FromMeta)]
51struct FilterMeta {
52    #[darling(default)]
53    op: Option<FilterOp>,
54    #[darling(default)]
55    column: Option<String>,
56}
57
58impl FromMeta for FilterOp {
59    fn from_string(value: &str) -> darling::Result<Self> {
60        match value {
61            "eq" => Ok(Self::Eq),
62            "ne" => Ok(Self::Ne),
63            "lt" => Ok(Self::Lt),
64            "lte" => Ok(Self::Lte),
65            "gt" => Ok(Self::Gt),
66            "gte" => Ok(Self::Gte),
67            "like" => Ok(Self::Like),
68            "ilike" => Ok(Self::ILike),
69            "in" => Ok(Self::In),
70            _ => Err(darling::Error::unknown_value(value)),
71        }
72    }
73}
74
75pub fn derive_filterable(
76    input: proc_macro2::TokenStream,
77    runtime_path: proc_macro2::TokenStream,
78) -> proc_macro2::TokenStream {
79    let input = match syn::parse2::<DeriveInput>(input) {
80        Ok(input) => input,
81        Err(err) => return err.to_compile_error(),
82    };
83    derive_filterable_impl(&input, runtime_path)
84}
85
86fn derive_filterable_impl(
87    input: &DeriveInput,
88    runtime_path: proc_macro2::TokenStream,
89) -> proc_macro2::TokenStream {
90    let ident = &input.ident;
91    let crate_path = crate::runtime_path(input, runtime_path);
92    let model = match parse_model(input) {
93        Ok(model) => model,
94        Err(e) => return e.to_compile_error(),
95    };
96    let fields = match named_fields(input) {
97        Ok(fields) => fields,
98        Err(e) => return e.to_compile_error(),
99    };
100    let filter_stmts = match filter_statements(fields, &crate_path) {
101        Ok(stmts) => stmts,
102        Err(e) => return e.to_compile_error(),
103    };
104    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
105
106    quote! {
107        impl #impl_generics #crate_path::Filterable for #ident #ty_generics #where_clause {
108            type Model = #model;
109
110            fn apply_filter(
111                &self,
112                mut filter: #crate_path::FilterBuilder<Self::Model>,
113            ) -> #crate_path::FilterBuilder<Self::Model> {
114                #(#filter_stmts)*
115                filter
116            }
117        }
118    }
119}
120
121fn named_fields(
122    input: &DeriveInput,
123) -> Result<&syn::punctuated::Punctuated<syn::Field, syn::Token![,]>, Error> {
124    match &input.data {
125        syn::Data::Struct(data) => match &data.fields {
126            syn::Fields::Named(fields) => Ok(&fields.named),
127            _ => Err(Error::new_spanned(
128                &input.ident,
129                "Filterable supports named structs only",
130            )),
131        },
132        _ => Err(Error::new_spanned(
133            &input.ident,
134            "Filterable supports structs only",
135        )),
136    }
137}
138
139fn filter_statements(
140    fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
141    crate_path: &proc_macro2::TokenStream,
142) -> Result<Vec<proc_macro2::TokenStream>, Error> {
143    let mut stmts = Vec::new();
144    for field in fields {
145        let Some(field_ident) = field.ident.as_ref() else {
146            continue;
147        };
148        let Some(attr) = parse_filter_attr(field)? else {
149            continue;
150        };
151        let column = match attr.column {
152            Some(column) => {
153                syn::parse_str::<Ident>(&column)
154                    .map_err(|_| Error::new(attr.span, "column must be a Rust field identifier"))?;
155                Ident::new(&column, attr.span)
156            }
157            None => field_ident.clone(),
158        };
159        stmts.push(filter_statement(
160            field_ident,
161            &field.ty,
162            &column,
163            attr.op,
164            crate_path,
165        )?);
166    }
167    Ok(stmts)
168}
169
170fn filter_statement(
171    field: &Ident,
172    ty: &Type,
173    column: &Ident,
174    op: FilterOp,
175    crate_path: &proc_macro2::TokenStream,
176) -> Result<proc_macro2::TokenStream, Error> {
177    if op == FilterOp::In {
178        return in_filter_statement(field, ty, column, crate_path);
179    }
180    let method = op.method();
181    if option_inner_type(ty).is_some() {
182        return Ok(quote! {
183            if let Some(value) = &self.#field {
184                let predicate = filter.#column.#method(#crate_path::val(value.clone()));
185                filter = filter.filter(predicate);
186            }
187        });
188    }
189    Ok(quote! {
190        let predicate = filter.#column.#method(#crate_path::val(self.#field.clone()));
191        filter = filter.filter(predicate);
192    })
193}
194
195fn in_filter_statement(
196    field: &Ident,
197    ty: &Type,
198    column: &Ident,
199    crate_path: &proc_macro2::TokenStream,
200) -> Result<proc_macro2::TokenStream, Error> {
201    if let Some(inner) = option_inner_type(ty) {
202        if vec_inner_type(inner).is_none() {
203            return Err(Error::new(
204                ty.span(),
205                "the in filter operator requires Vec<T> or Option<Vec<T>>",
206            ));
207        }
208        return Ok(quote! {
209            if let Some(values) = &self.#field {
210                if !values.is_empty() {
211                    let predicate = #crate_path::filters::__private::in_values(
212                        &filter.#column,
213                        values.iter().cloned().map(#crate_path::val),
214                    );
215                    filter = filter.filter(predicate);
216                }
217            }
218        });
219    }
220    if vec_inner_type(ty).is_none() {
221        return Err(Error::new(
222            ty.span(),
223            "the in filter operator requires Vec<T> or Option<Vec<T>>",
224        ));
225    }
226    Ok(quote! {
227        if !self.#field.is_empty() {
228            let predicate = #crate_path::filters::__private::in_values(
229                &filter.#column,
230                self.#field.iter().cloned().map(#crate_path::val),
231            );
232            filter = filter.filter(predicate);
233        }
234    })
235}
236
237fn parse_model(input: &DeriveInput) -> Result<Path, Error> {
238    let mut model = None;
239    for attr in input
240        .attrs
241        .iter()
242        .filter(|attr| attr.path().is_ident("filter"))
243    {
244        let nested = parse_nested(attr)?;
245        let parsed = FilterContainer::from_list(&nested)
246            .map_err(|err| Error::new(attr.span(), err.to_string()))?;
247        if model.is_some() && parsed.model.is_some() {
248            return Err(Error::new(attr.span(), "model can only be set once"));
249        }
250        model = model.or(parsed.model);
251    }
252    model.ok_or_else(|| {
253        Error::new_spanned(
254            &input.ident,
255            "Filterable requires #[filter(model = ModelType)]",
256        )
257    })
258}
259
260fn parse_filter_attr(field: &syn::Field) -> Result<Option<FilterAttr>, Error> {
261    let attrs: Vec<_> = field
262        .attrs
263        .iter()
264        .filter(|attr| attr.path().is_ident("filter"))
265        .collect();
266    if attrs.is_empty() {
267        return Ok(None);
268    }
269    if attrs.len() > 1 {
270        return Err(Error::new(
271            attrs[1].span(),
272            "only one #[filter(...)] attribute is supported per field",
273        ));
274    }
275    let nested = parse_nested(attrs[0])?;
276    let parsed = FilterMeta::from_list(&nested)
277        .map_err(|err| Error::new(attrs[0].span(), err.to_string()))?;
278    Ok(Some(FilterAttr {
279        op: parsed.op.unwrap_or(FilterOp::Eq),
280        column: parsed.column,
281        span: attrs[0].span(),
282    }))
283}
284
285fn parse_nested(attr: &syn::Attribute) -> Result<Vec<darling::ast::NestedMeta>, Error> {
286    match &attr.meta {
287        syn::Meta::List(list) => darling::ast::NestedMeta::parse_meta_list(list.tokens.clone())
288            .map_err(|err| Error::new(attr.span(), err.to_string())),
289        syn::Meta::Path(_) => Ok(Vec::new()),
290        _ => Err(Error::new(attr.span(), "expected #[filter(...)]")),
291    }
292}
293
294fn option_inner_type(ty: &Type) -> Option<&Type> {
295    let Type::Path(path) = ty else {
296        return None;
297    };
298    let segment = path.path.segments.last()?;
299    if segment.ident != "Option" {
300        return None;
301    }
302    let PathArguments::AngleBracketed(args) = &segment.arguments else {
303        return None;
304    };
305    let Some(GenericArgument::Type(inner)) = args.args.first() else {
306        return None;
307    };
308    Some(inner)
309}
310
311fn vec_inner_type(ty: &Type) -> Option<&Type> {
312    let Type::Path(path) = ty else {
313        return None;
314    };
315    let segment = path.path.segments.last()?;
316    if segment.ident != "Vec" {
317        return None;
318    }
319    let PathArguments::AngleBracketed(args) = &segment.arguments else {
320        return None;
321    };
322    let Some(GenericArgument::Type(inner)) = args.args.first() else {
323        return None;
324    };
325    Some(inner)
326}