1use 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
25macro_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#[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 SubmittedBy, SUBMITTED_BY, "submitted_by", Eq, NotEq, In, Cmp, OrderBy
232 );
233
234 pub fn with_limit(limit: usize) -> Filter {
242 Filter::with_limit(limit)
243 }
244
245 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 fn eq<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
264}
265
266pub trait NotEq: sealed::FilterPriv {
267 fn ne<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
269}
270
271pub trait Like: sealed::FilterPriv {
272 fn like<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
274}
275
276pub trait NotLike: sealed::FilterPriv {
277 fn not_like<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
279}
280
281pub trait In: sealed::FilterPriv {
282 fn _in<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
284}
285
286pub trait NotIn: sealed::FilterPriv {
287 fn not_in<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
289}
290
291pub trait Cmp: sealed::FilterPriv {
292 fn le<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
294
295 fn lt<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
297
298 fn ge<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
300
301 fn gt<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
303}
304
305pub trait BitwiseAnd: sealed::FilterPriv {
306 fn bit_and<T: fmt::Display, V: Into<OneOrMany<T>>>(value: V) -> Filter;
308}
309
310pub trait OrderBy: sealed::FilterPriv {
311 fn asc() -> Filter;
313
314 fn desc() -> Filter;
316}
317
318pub 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
336pub fn custom_order_by_asc<S: Into<Cow<'static, str>>>(name: S) -> Filter {
344 Filter::new_order_by_asc(name)
345}
346
347pub 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#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
541pub enum Operator {
542 Equals,
544 Not,
546 Like,
548 NotLike,
550 In,
552 NotIn,
554 Min,
556 Max,
558 SmallerThan,
560 GreaterThan,
562 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#[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