modio/
filter.rs

1//! Filtering and sorting
2use std::collections::BTreeMap;
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: BTreeMap<(String, Operator), OneOrMany<String>>,
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 = BTreeMap::new();
371        filters.insert((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 len = self.filters.len()
477            + self.limit.as_ref().map(|_| 1).unwrap_or_default()
478            + self.offset.as_ref().map(|_| 1).unwrap_or_default()
479            + self.order_by.as_ref().map(|_| 1).unwrap_or_default();
480
481        let mut map = serializer.serialize_map(Some(len))?;
482        for ((name, op), value) in &self.filters {
483            map.serialize_key(&format!("{name}{op}"))?;
484            match value {
485                OneOrMany::One(v) => map.serialize_value(v)?,
486                OneOrMany::Many(v) => {
487                    let mut iter = v.iter().peekable();
488                    let mut value = String::new();
489
490                    while let Some(s) = iter.next() {
491                        value.push_str(s);
492
493                        if iter.peek().is_some() {
494                            value.push(',');
495                        }
496                    }
497
498                    map.serialize_value(&value)?;
499                }
500            }
501        }
502        if let Some(ref limit) = self.limit {
503            map.serialize_entry("_limit", limit)?;
504        }
505        if let Some(ref offset) = self.offset {
506            map.serialize_entry("_offset", offset)?;
507        }
508        if let Some(ref order_by) = self.order_by {
509            map.serialize_entry("_sort", &order_by.to_string())?;
510        }
511        map.end()
512    }
513}
514
515#[derive(Clone)]
516enum Sorting {
517    Asc(String),
518    Desc(String),
519}
520
521impl fmt::Display for Sorting {
522    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523        use std::fmt::Write;
524
525        match self {
526            Self::Asc(field) => f.write_str(field),
527            Self::Desc(field) => {
528                f.write_char('-')?;
529                f.write_str(field)
530            }
531        }
532    }
533}
534
535/// Filter operators of mod.io.
536///
537/// See [mod.io docs](https://docs.mod.io/restapiref/#filtering) for more information.
538#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
539pub enum Operator {
540    /// Equal to (`id=1`)
541    Equals,
542    /// Not equal to (`id-not=1`)
543    Not,
544    /// Equivalent to SQL's `LIKE`. `*` is equivalent to SQL's `%`. (`name-lk=foo*`)
545    Like,
546    /// Equivalent to SQL's `NOT LIKE` (`name-not-lk=foo*`)
547    NotLike,
548    /// Equivalent to SQL's `IN` (`id-in=1,3,5`)
549    In,
550    /// Equivalent to SQL's `NOT IN` (`id-not-in=1,3,5`)
551    NotIn,
552    /// Greater than or equal to (`id-min=5`)
553    Min,
554    /// Less than or equal to (`id-max=10`)
555    Max,
556    /// Less than (`id-st=10`)
557    SmallerThan,
558    /// Greater than (`id-gt=5`)
559    GreaterThan,
560    /// Match bits (`maturity_option-bitwise-and=5`)
561    BitwiseAnd,
562}
563
564impl fmt::Display for Operator {
565    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
566        match self {
567            Self::Equals => fmt.write_str(""),
568            Self::Not => fmt.write_str("-not"),
569            Self::Like => fmt.write_str("-lk"),
570            Self::NotLike => fmt.write_str("-not-lk"),
571            Self::In => fmt.write_str("-in"),
572            Self::NotIn => fmt.write_str("-not-in"),
573            Self::Min => fmt.write_str("-min"),
574            Self::Max => fmt.write_str("-max"),
575            Self::SmallerThan => fmt.write_str("-st"),
576            Self::GreaterThan => fmt.write_str("-gt"),
577            Self::BitwiseAnd => fmt.write_str("-bitwise-and"),
578        }
579    }
580}
581
582/// Represents a value or a list of values of a filter.
583#[derive(Clone, Debug)]
584pub enum OneOrMany<T>
585where
586    T: fmt::Display,
587{
588    One(T),
589    Many(Vec<T>),
590}
591
592impl<T: fmt::Display> OneOrMany<T> {
593    fn to_string(&self) -> OneOrMany<String> {
594        match self {
595            Self::One(s) => OneOrMany::One(s.to_string()),
596            Self::Many(s) => OneOrMany::Many(s.iter().map(ToString::to_string).collect()),
597        }
598    }
599}
600
601impl<T: fmt::Display> From<T> for OneOrMany<T> {
602    fn from(from: T) -> OneOrMany<T> {
603        Self::One(from)
604    }
605}
606
607impl<T: fmt::Display> From<Vec<T>> for OneOrMany<T> {
608    fn from(from: Vec<T>) -> OneOrMany<T> {
609        Self::Many(from)
610    }
611}
612
613#[cfg(test)]
614mod test {
615    #[test]
616    #[allow(dead_code)]
617    fn filters() {
618        use super::prelude::*;
619
620        filter!(GameId, GAME_ID, "game_id", Eq, NotEq, Like, In, Cmp, OrderBy);
621        filter!(NameId, NAME_ID, "name_id", Eq, NotEq, Like, In, Cmp, OrderBy);
622        filter!(BitOption, BIT_OPTION, "bit_option", Bit);
623
624        assert_eq!(GAME_ID, "game_id");
625        assert_eq!(NAME_ID, "name_id");
626
627        let f = GameId::eq(1);
628        assert_eq!(f.to_string(), r#"{"game_id":"1"}"#);
629
630        let f = GameId::_in(vec![1, 2]).and(NameId::like("foobar*"));
631        assert_eq!(
632            f.to_string(),
633            r#"{"game_id-in":"1,2","name_id-lk":"foobar*"}"#
634        );
635
636        let f = GameId::eq(1).and(GameId::eq(2)).and(GameId::ne(3));
637        assert_eq!(f.to_string(), r#"{"game_id":"2","game_id-not":"3"}"#);
638
639        let f = GameId::eq(1).order_by(NameId::asc());
640        assert_eq!(f.to_string(), r#"{"game_id":"1","_sort":"name_id"}"#);
641
642        let f = NameId::like("foo*").and(NameId::not_like("bar*"));
643        assert_eq!(
644            f.to_string(),
645            r#"{"name_id-lk":"foo*","name_id-not-lk":"bar*"}"#
646        );
647
648        let f = NameId::gt(1).and(NameId::lt(2));
649        assert_eq!(f.to_string(), r#"{"name_id-st":"2","name_id-gt":"1"}"#);
650
651        let f = NameId::ge(1).and(NameId::le(2));
652        assert_eq!(f.to_string(), r#"{"name_id-min":"1","name_id-max":"2"}"#);
653
654        let f = BitOption::bit_and(1);
655        assert_eq!(f.to_string(), r#"{"bit_option-bitwise-and":"1"}"#);
656
657        let f = NameId::desc();
658        assert_eq!(f.to_string(), r#"{"_sort":"-name_id"}"#);
659
660        let f = with_limit(10).and(with_limit(20));
661        assert_eq!(f.to_string(), r#"{"_limit":20}"#);
662
663        let f = with_offset(10).and(with_offset(20));
664        assert_eq!(f.to_string(), r#"{"_offset":20}"#);
665    }
666
667    #[test]
668    fn custom_filters() {
669        use super::prelude::*;
670        use super::*;
671
672        filter!(GameId, GAME_ID, "game_id", Eq);
673
674        let f = GameId::eq(1).and(custom_filter("foo", Operator::Equals, "bar"));
675        assert_eq!(f.to_string(), r#"{"foo":"bar","game_id":"1"}"#);
676
677        let f = custom_order_by_asc("foo");
678        assert_eq!(f.to_string(), r#"{"_sort":"foo"}"#);
679    }
680
681    #[test]
682    fn std_ops_add() {
683        use super::prelude::*;
684
685        let f = Id::eq(1) + Id::eq(2);
686        assert_eq!(f.to_string(), r#"{"id":"2"}"#);
687
688        let f = Id::eq(1) + NameId::eq("foo");
689        assert_eq!(f.to_string(), r#"{"id":"1","name_id":"foo"}"#);
690    }
691}
692
693// vim: fdm=marker