modio/request/
filter.rs

1//! Filtering and sorting
2use std::borrow::Cow;
3use std::collections::BTreeMap;
4use std::fmt;
5
6macro_rules! filter {
7    ($type:ident, $name:ident, $value:expr) => {
8        static $name: &str = $value;
9        pub struct $type;
10    };
11    (
12        $(#[$outer:meta])*
13        $type:ident, $name:ident, $value:expr, $($x:tt),*) => {
14        static $name: &str = $value;
15        $(#[$outer])*
16        pub struct $type;
17        $(
18            __impl_filter!($x, $type, $name);
19        )*
20
21        impl crate::request::filter::sealed::FilterPriv for $type {}
22    };
23}
24
25/// macros: `__impl_filter_*` {{{
26macro_rules! __impl_filter {
27    (Eq, $type:ident, $name:ident) => {
28        __impl_filter_eq!($type, $name);
29    };
30    (NotEq, $type:ident, $name:ident) => {
31        __impl_filter_ne!($type, $name);
32    };
33    (Like, $type:ident, $name:ident) => {
34        __impl_filter_like!($type, $name);
35    };
36    (In, $type:ident, $name:ident) => {
37        __impl_filter_in!($type, $name);
38    };
39    (Cmp, $type:ident, $name:ident) => {
40        __impl_filter_cmp!($type, $name);
41    };
42    (Bit, $type:ident, $name:ident) => {
43        __impl_filter_bit!($type, $name);
44    };
45    (OrderBy, $type:ident, $name:ident) => {
46        __impl_filter_order_by!($type, $name);
47    };
48}
49
50macro_rules! __impl_filter_eq {
51    ($type:ty, $name:expr) => {
52        impl crate::request::filter::Eq for $type {
53            fn eq<T, V>(value: V) -> crate::request::filter::Filter
54            where
55                T: std::fmt::Display,
56                V: Into<crate::request::filter::OneOrMany<T>>,
57            {
58                let op = crate::request::filter::Operator::Equals;
59                crate::request::filter::Filter::new($name, op, value)
60            }
61        }
62    };
63}
64
65macro_rules! __impl_filter_ne {
66    ($type:ty, $name:expr) => {
67        impl crate::request::filter::NotEq for $type {
68            fn ne<T, V>(value: V) -> crate::request::filter::Filter
69            where
70                T: std::fmt::Display,
71                V: Into<crate::request::filter::OneOrMany<T>>,
72            {
73                let op = crate::request::filter::Operator::Not;
74                crate::request::filter::Filter::new($name, op, value)
75            }
76        }
77    };
78}
79
80macro_rules! __impl_filter_like {
81    ($type:ty, $name:expr) => {
82        impl crate::request::filter::Like for $type {
83            fn like<T, V>(value: V) -> crate::request::filter::Filter
84            where
85                T: std::fmt::Display,
86                V: Into<crate::request::filter::OneOrMany<T>>,
87            {
88                let op = crate::request::filter::Operator::Like;
89                crate::request::filter::Filter::new($name, op, value)
90            }
91        }
92
93        impl crate::request::filter::NotLike for $type {
94            fn not_like<T, V>(value: V) -> crate::request::filter::Filter
95            where
96                T: std::fmt::Display,
97                V: Into<crate::request::filter::OneOrMany<T>>,
98            {
99                let op = crate::request::filter::Operator::NotLike;
100                crate::request::filter::Filter::new($name, op, value)
101            }
102        }
103    };
104}
105
106macro_rules! __impl_filter_in {
107    ($type:ty, $name:expr) => {
108        impl crate::request::filter::In for $type {
109            fn _in<T, V>(value: V) -> crate::request::filter::Filter
110            where
111                T: std::fmt::Display,
112                V: Into<crate::request::filter::OneOrMany<T>>,
113            {
114                let op = crate::request::filter::Operator::In;
115                crate::request::filter::Filter::new($name, op, value)
116            }
117        }
118
119        impl crate::request::filter::NotIn for $type {
120            fn not_in<T, V>(value: V) -> crate::request::filter::Filter
121            where
122                T: std::fmt::Display,
123                V: Into<crate::request::filter::OneOrMany<T>>,
124            {
125                let op = crate::request::filter::Operator::NotIn;
126                crate::request::filter::Filter::new($name, op, value)
127            }
128        }
129    };
130}
131
132macro_rules! __impl_filter_cmp {
133    ($type:ty, $name:expr) => {
134        impl crate::request::filter::Cmp for $type {
135            fn le<T, V>(value: V) -> crate::request::filter::Filter
136            where
137                T: std::fmt::Display,
138                V: Into<crate::request::filter::OneOrMany<T>>,
139            {
140                let op = crate::request::filter::Operator::Max;
141                crate::request::filter::Filter::new($name, op, value)
142            }
143
144            fn ge<T, V>(value: V) -> crate::request::filter::Filter
145            where
146                T: std::fmt::Display,
147                V: Into<crate::request::filter::OneOrMany<T>>,
148            {
149                let op = crate::request::filter::Operator::Min;
150                crate::request::filter::Filter::new($name, op, value)
151            }
152
153            fn gt<T, V>(value: V) -> crate::request::filter::Filter
154            where
155                T: std::fmt::Display,
156                V: Into<crate::request::filter::OneOrMany<T>>,
157            {
158                let op = crate::request::filter::Operator::GreaterThan;
159                crate::request::filter::Filter::new($name, op, value)
160            }
161
162            fn lt<T, V>(value: V) -> crate::request::filter::Filter
163            where
164                T: std::fmt::Display,
165                V: Into<crate::request::filter::OneOrMany<T>>,
166            {
167                let op = crate::request::filter::Operator::SmallerThan;
168                crate::request::filter::Filter::new($name, op, value)
169            }
170        }
171    };
172}
173
174macro_rules! __impl_filter_bit {
175    ($type:ty, $name:expr) => {
176        impl crate::request::filter::BitwiseAnd for $type {
177            fn bit_and<T, V>(value: V) -> crate::request::filter::Filter
178            where
179                T: std::fmt::Display,
180                V: Into<crate::request::filter::OneOrMany<T>>,
181            {
182                let op = crate::request::filter::Operator::BitwiseAnd;
183                crate::request::filter::Filter::new($name, op, value)
184            }
185        }
186    };
187}
188
189macro_rules! __impl_filter_order_by {
190    ($type:ty, $name:expr) => {
191        impl crate::request::filter::OrderBy for $type {
192            fn asc() -> crate::request::filter::Filter {
193                crate::request::filter::Filter::new_order_by_asc($name)
194            }
195
196            fn desc() -> crate::request::filter::Filter {
197                crate::request::filter::Filter::new_order_by_desc($name)
198            }
199        }
200    };
201}
202// }}}
203
204/// A `prelude` for using common filters and importing traits.
205/// ```
206/// use modio::request::filter::prelude::*;
207/// ```
208#[rustfmt::skip]
209pub mod prelude {
210    pub use super::BitwiseAnd;
211    pub use super::Cmp;
212    pub use super::OrderBy;
213    pub use super::{Eq, NotEq};
214    pub use super::{In, NotIn};
215    pub use super::{Like, NotLike};
216
217    pub use super::Filter;
218    pub use super::OneOrMany;
219
220    filter!(Fulltext, _Q, "_q", Eq);
221    filter!(Id, ID, "id", Eq, NotEq, In, Cmp, OrderBy);
222    filter!(Name, NAME, "name", Eq, NotEq, Like, In, OrderBy);
223    filter!(NameId, NAME_ID, "name_id", Eq, NotEq, Like, In, OrderBy);
224    filter!(ModId, MOD_ID, "mod_id", Eq, NotEq, In, Cmp, OrderBy);
225    filter!(Status, STATUS, "status", Eq, NotEq, In, Cmp, OrderBy);
226    filter!(DateAdded, DATE_ADDED, "date_added", Eq, NotEq, In, Cmp, OrderBy);
227    filter!(DateUpdated, DATE_UPDATED, "date_updated", Eq, NotEq, In, Cmp, OrderBy);
228    filter!(DateLive, DATE_LIVE, "date_live", Eq, NotEq, In, Cmp, OrderBy);
229    filter!(
230        /// Unique id of the user who has ownership of the objects.
231        SubmittedBy, SUBMITTED_BY, "submitted_by", Eq, NotEq, In, Cmp, OrderBy
232    );
233
234    /// Create a `Filter` with a limit to paginate through results.
235    ///
236    /// ```
237    /// use modio::request::filter::prelude::*;
238    ///
239    /// let filter = with_limit(10).offset(10);
240    /// ```
241    pub fn with_limit(limit: usize) -> Filter {
242        Filter::with_limit(limit)
243    }
244
245    /// Create a `Filter` with an offset to paginate through results.
246    ///
247    /// ```
248    /// use modio::request::filter::prelude::*;
249    ///
250    /// let filter = with_offset(10).limit(10);
251    /// ```
252    pub fn with_offset(offset: usize) -> Filter {
253        Filter::with_offset(offset)
254    }
255}
256
257pub(crate) mod sealed {
258    pub trait FilterPriv {}
259}
260
261pub trait Eq: sealed::FilterPriv {
262    /// Creates [`Equals`](Operator::Equals) filter.
263    fn eq<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
264}
265
266pub trait NotEq: sealed::FilterPriv {
267    /// Creates [`Not`](Operator::Not) filter.
268    fn ne<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
269}
270
271pub trait Like: sealed::FilterPriv {
272    /// Creates [`Like`](Operator::Like) filter.
273    fn like<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
274}
275
276pub trait NotLike: sealed::FilterPriv {
277    /// Creates [`NotLike`](Operator::Like) filter.
278    fn not_like<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
279}
280
281pub trait In: sealed::FilterPriv {
282    /// Creates [`In`](Operator::In) filter.
283    fn _in<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
284}
285
286pub trait NotIn: sealed::FilterPriv {
287    /// Creates [`NotIn`](Operator::NotIn) filter.
288    fn not_in<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
289}
290
291pub trait Cmp: sealed::FilterPriv {
292    /// Creates [`Max`](Operator::Max) filter.
293    fn le<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
294
295    /// Creates [`SmallerThan`](Operator::SmallerThan) filter.
296    fn lt<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
297
298    /// Creates [`Min`](Operator::Min) filter.
299    fn ge<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
300
301    /// Creates [`GreaterThan`](Operator::GreaterThan) filter.
302    fn gt<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
303}
304
305pub trait BitwiseAnd: sealed::FilterPriv {
306    /// Creates [`BitwiseAnd`](Operator::BitwiseAnd) filter.
307    fn bit_and<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
308}
309
310pub trait OrderBy: sealed::FilterPriv {
311    /// Creates sorting filter in ascending order.
312    fn asc() -> Filter;
313
314    /// Creates sorting filter in descending order.
315    fn desc() -> Filter;
316}
317
318/// Create a custom `Filter`.
319///
320/// ```
321/// use modio::request::filter::{custom_filter, Operator};
322///
323/// let filter = custom_filter("foo", Operator::Equals, "bar");
324///
325/// let filter = custom_filter(String::from("bar"), Operator::Equals, "foo");
326/// ```
327pub fn custom_filter<S, T, V>(name: S, op: Operator, value: V) -> Filter
328where
329    S: Into<Cow<'static, str>>,
330    T: fmt::Display,
331    V: Into<OneOrMany<T>>,
332{
333    Filter::new(name, op, value)
334}
335
336/// Create a custom sorting `Filter` in ascending order.
337///
338/// ```
339/// use modio::request::filter::{custom_filter, custom_order_by_asc, Operator};
340///
341/// let filter = custom_filter("foo", Operator::Like, "bar*").order_by(custom_order_by_asc("foo"));
342/// ```
343pub fn custom_order_by_asc<S: Into<Cow<'static, str>>>(name: S) -> Filter {
344    Filter::new_order_by_asc(name)
345}
346
347/// Create a custom sorting `Filter` in descending order.
348///
349/// ```
350/// use modio::request::filter::{custom_filter, custom_order_by_desc, Operator};
351///
352/// let filter = custom_filter("foo", Operator::Like, "bar*").order_by(custom_order_by_desc("foo"));
353/// ```
354pub fn custom_order_by_desc<S: Into<Cow<'static, str>>>(name: S) -> Filter {
355    Filter::new_order_by_desc(name)
356}
357
358#[derive(Clone, Default)]
359pub struct Filter {
360    filters: BTreeMap<(Cow<'static, str>, Operator), OneOrMany<String>>,
361    order_by: Option<Sorting>,
362    limit: Option<usize>,
363    offset: Option<usize>,
364}
365
366impl Filter {
367    pub(crate) fn new<S, T, V>(name: S, op: Operator, value: V) -> Filter
368    where
369        S: Into<Cow<'static, str>>,
370        T: fmt::Display,
371        V: Into<OneOrMany<T>>,
372    {
373        let mut filters = BTreeMap::new();
374        filters.insert((name.into(), op), value.into().to_string());
375        Filter {
376            filters,
377            ..Default::default()
378        }
379    }
380
381    pub(crate) fn new_order_by_asc<S>(name: S) -> Filter
382    where
383        S: Into<Cow<'static, str>>,
384    {
385        Filter {
386            order_by: Some(Sorting::Asc(name.into())),
387            ..Default::default()
388        }
389    }
390
391    pub(crate) fn new_order_by_desc<S>(name: S) -> Filter
392    where
393        S: Into<Cow<'static, str>>,
394    {
395        Filter {
396            order_by: Some(Sorting::Desc(name.into())),
397            ..Default::default()
398        }
399    }
400
401    pub(crate) fn with_limit(limit: usize) -> Filter {
402        Filter {
403            limit: Some(limit),
404            ..Default::default()
405        }
406    }
407
408    pub(crate) fn with_offset(offset: usize) -> Filter {
409        Filter {
410            offset: Some(offset),
411            ..Default::default()
412        }
413    }
414
415    #[must_use]
416    pub fn and(self, mut other: Filter) -> Filter {
417        let Filter { mut filters, .. } = self;
418        filters.append(&mut other.filters);
419        Filter {
420            filters,
421            order_by: other.order_by.or(self.order_by),
422            limit: other.limit.or(self.limit),
423            offset: other.offset.or(self.offset),
424        }
425    }
426
427    #[must_use]
428    pub fn order_by(self, other: Filter) -> Filter {
429        Filter {
430            order_by: other.order_by.or(self.order_by),
431            ..self
432        }
433    }
434
435    #[must_use]
436    pub fn limit(self, limit: usize) -> Filter {
437        Filter {
438            limit: Some(limit),
439            ..self
440        }
441    }
442
443    #[must_use]
444    pub fn offset(self, offset: usize) -> Filter {
445        Filter {
446            offset: Some(offset),
447            ..self
448        }
449    }
450}
451
452impl std::ops::Add for Filter {
453    type Output = Self;
454
455    fn add(self, other: Self) -> Self {
456        self.and(other)
457    }
458}
459
460impl fmt::Display for Filter {
461    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462        match serde_json::to_string(&self) {
463            Ok(s) => f.write_str(&s),
464            Err(_) => Err(fmt::Error),
465        }
466    }
467}
468
469impl sealed::FilterPriv for Filter {}
470
471#[doc(hidden)]
472impl serde::ser::Serialize for Filter {
473    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
474    where
475        S: serde::ser::Serializer,
476    {
477        use serde::ser::SerializeMap;
478
479        let len = self.filters.len()
480            + self.limit.as_ref().map(|_| 1).unwrap_or_default()
481            + self.offset.as_ref().map(|_| 1).unwrap_or_default()
482            + self.order_by.as_ref().map(|_| 1).unwrap_or_default();
483
484        let mut map = serializer.serialize_map(Some(len))?;
485        for ((name, op), value) in &self.filters {
486            map.serialize_key(&format!("{name}{op}"))?;
487            match value {
488                OneOrMany::One(v) => map.serialize_value(v)?,
489                OneOrMany::Many(v) => {
490                    let mut iter = v.iter().peekable();
491                    let mut value = String::new();
492
493                    while let Some(s) = iter.next() {
494                        value.push_str(s);
495
496                        if iter.peek().is_some() {
497                            value.push(',');
498                        }
499                    }
500
501                    map.serialize_value(&value)?;
502                }
503            }
504        }
505        if let Some(ref limit) = self.limit {
506            map.serialize_entry("_limit", limit)?;
507        }
508        if let Some(ref offset) = self.offset {
509            map.serialize_entry("_offset", offset)?;
510        }
511        if let Some(ref order_by) = self.order_by {
512            map.serialize_entry("_sort", order_by)?;
513        }
514        map.end()
515    }
516}
517
518#[derive(Clone)]
519enum Sorting {
520    Asc(Cow<'static, str>),
521    Desc(Cow<'static, str>),
522}
523
524impl serde::ser::Serialize for Sorting {
525    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
526        match self {
527            Sorting::Asc(asc) => serializer.serialize_str(asc),
528            Sorting::Desc(desc) => {
529                let mut s = String::from('-');
530                s.push_str(desc);
531                serializer.serialize_str(&s)
532            }
533        }
534    }
535}
536
537/// Filter operators of mod.io.
538///
539/// See [mod.io docs](https://docs.mod.io/restapiref/#filtering) for more information.
540#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
541pub enum Operator {
542    /// Equal to (`id=1`)
543    Equals,
544    /// Not equal to (`id-not=1`)
545    Not,
546    /// Equivalent to SQL's `LIKE`. `*` is equivalent to SQL's `%`. (`name-lk=foo*`)
547    Like,
548    /// Equivalent to SQL's `NOT LIKE` (`name-not-lk=foo*`)
549    NotLike,
550    /// Equivalent to SQL's `IN` (`id-in=1,3,5`)
551    In,
552    /// Equivalent to SQL's `NOT IN` (`id-not-in=1,3,5`)
553    NotIn,
554    /// Greater than or equal to (`id-min=5`)
555    Min,
556    /// Less than or equal to (`id-max=10`)
557    Max,
558    /// Less than (`id-st=10`)
559    SmallerThan,
560    /// Greater than (`id-gt=5`)
561    GreaterThan,
562    /// Match bits (`maturity_option-bitwise-and=5`)
563    BitwiseAnd,
564}
565
566impl fmt::Display for Operator {
567    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
568        match self {
569            Self::Equals => fmt.write_str(""),
570            Self::Not => fmt.write_str("-not"),
571            Self::Like => fmt.write_str("-lk"),
572            Self::NotLike => fmt.write_str("-not-lk"),
573            Self::In => fmt.write_str("-in"),
574            Self::NotIn => fmt.write_str("-not-in"),
575            Self::Min => fmt.write_str("-min"),
576            Self::Max => fmt.write_str("-max"),
577            Self::SmallerThan => fmt.write_str("-st"),
578            Self::GreaterThan => fmt.write_str("-gt"),
579            Self::BitwiseAnd => fmt.write_str("-bitwise-and"),
580        }
581    }
582}
583
584/// Represents a value or a list of values of a filter.
585#[derive(Clone, Debug)]
586pub enum OneOrMany<T>
587where
588    T: fmt::Display,
589{
590    One(T),
591    Many(Vec<T>),
592}
593
594impl<T: fmt::Display> OneOrMany<T> {
595    fn to_string(&self) -> OneOrMany<String> {
596        match self {
597            Self::One(s) => OneOrMany::One(s.to_string()),
598            Self::Many(s) => OneOrMany::Many(s.iter().map(ToString::to_string).collect()),
599        }
600    }
601}
602
603impl<T: fmt::Display> From<T> for OneOrMany<T> {
604    fn from(from: T) -> OneOrMany<T> {
605        Self::One(from)
606    }
607}
608
609impl<T: fmt::Display> From<Vec<T>> for OneOrMany<T> {
610    fn from(from: Vec<T>) -> OneOrMany<T> {
611        Self::Many(from)
612    }
613}
614
615#[cfg(test)]
616mod test {
617    #[test]
618    #[allow(dead_code)]
619    fn filters() {
620        use super::prelude::*;
621
622        filter!(GameId, GAME_ID, "game_id", Eq, NotEq, Like, In, Cmp, OrderBy);
623        filter!(NameId, NAME_ID, "name_id", Eq, NotEq, Like, In, Cmp, OrderBy);
624        filter!(BitOption, BIT_OPTION, "bit_option", Bit);
625
626        assert_eq!(GAME_ID, "game_id");
627        assert_eq!(NAME_ID, "name_id");
628
629        let f = GameId::eq(1);
630        assert_eq!(f.to_string(), r#"{"game_id":"1"}"#);
631
632        let f = GameId::_in(vec![1, 2]).and(NameId::like("foobar*"));
633        assert_eq!(
634            f.to_string(),
635            r#"{"game_id-in":"1,2","name_id-lk":"foobar*"}"#
636        );
637
638        let f = GameId::eq(1).and(GameId::eq(2)).and(GameId::ne(3));
639        assert_eq!(f.to_string(), r#"{"game_id":"2","game_id-not":"3"}"#);
640
641        let f = GameId::eq(1).order_by(NameId::asc());
642        assert_eq!(f.to_string(), r#"{"game_id":"1","_sort":"name_id"}"#);
643
644        let f = NameId::like("foo*").and(NameId::not_like("bar*"));
645        assert_eq!(
646            f.to_string(),
647            r#"{"name_id-lk":"foo*","name_id-not-lk":"bar*"}"#
648        );
649
650        let f = NameId::gt(1).and(NameId::lt(2));
651        assert_eq!(f.to_string(), r#"{"name_id-st":"2","name_id-gt":"1"}"#);
652
653        let f = NameId::ge(1).and(NameId::le(2));
654        assert_eq!(f.to_string(), r#"{"name_id-min":"1","name_id-max":"2"}"#);
655
656        let f = BitOption::bit_and(1);
657        assert_eq!(f.to_string(), r#"{"bit_option-bitwise-and":"1"}"#);
658
659        let f = NameId::desc();
660        assert_eq!(f.to_string(), r#"{"_sort":"-name_id"}"#);
661
662        let f = with_limit(10).and(with_limit(20));
663        assert_eq!(f.to_string(), r#"{"_limit":20}"#);
664
665        let f = with_offset(10).and(with_offset(20));
666        assert_eq!(f.to_string(), r#"{"_offset":20}"#);
667    }
668
669    #[test]
670    fn custom_filters() {
671        use super::prelude::*;
672        use super::*;
673
674        filter!(GameId, GAME_ID, "game_id", Eq);
675
676        let f = GameId::eq(1).and(custom_filter("foo", Operator::Equals, "bar"));
677        assert_eq!(f.to_string(), r#"{"foo":"bar","game_id":"1"}"#);
678
679        let f = custom_order_by_asc("foo");
680        assert_eq!(f.to_string(), r#"{"_sort":"foo"}"#);
681    }
682
683    #[test]
684    fn std_ops_add() {
685        use super::prelude::*;
686
687        let f = Id::eq(1) + Id::eq(2);
688        assert_eq!(f.to_string(), r#"{"id":"2"}"#);
689
690        let f = Id::eq(1) + NameId::eq("foo");
691        assert_eq!(f.to_string(), r#"{"id":"1","name_id":"foo"}"#);
692    }
693}
694
695// vim: fdm=marker