modio/
filter.rs

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