1use crate::filter::{Filter, FilterValue};
13use serde::{Deserialize, Serialize};
14
15pub trait ScalarFilter {
20 fn into_filter(self, column: &str) -> Filter;
23}
24
25pub(crate) fn combine_filters(parts: Vec<Filter>) -> Filter {
34 match parts.len() {
35 0 => Filter::None,
36 1 => parts.into_iter().next().unwrap(),
37 _ => Filter::and(parts),
38 }
39}
40
41#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
43pub enum QueryMode {
44 #[default]
46 Default,
47 Insensitive,
50}
51
52#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
54#[serde(rename_all = "snake_case")]
55pub struct StringFilter {
56 pub equals: Option<String>,
58 pub not: Option<Box<StringFilter>>,
60 pub in_list: Option<Vec<String>>,
62 pub not_in: Option<Vec<String>>,
64 pub lt: Option<String>,
66 pub lte: Option<String>,
68 pub gt: Option<String>,
70 pub gte: Option<String>,
72 pub contains: Option<String>,
74 pub starts_with: Option<String>,
76 pub ends_with: Option<String>,
78 pub mode: Option<QueryMode>,
80}
81
82impl StringFilter {
83 pub fn equals(v: impl Into<String>) -> Self {
85 Self {
86 equals: Some(v.into()),
87 ..Default::default()
88 }
89 }
90 pub fn contains(v: impl Into<String>) -> Self {
92 Self {
93 contains: Some(v.into()),
94 ..Default::default()
95 }
96 }
97 pub fn starts_with(v: impl Into<String>) -> Self {
99 Self {
100 starts_with: Some(v.into()),
101 ..Default::default()
102 }
103 }
104 pub fn ends_with(v: impl Into<String>) -> Self {
106 Self {
107 ends_with: Some(v.into()),
108 ..Default::default()
109 }
110 }
111}
112
113impl From<&str> for StringFilter {
114 fn from(v: &str) -> Self {
115 Self::equals(v)
116 }
117}
118impl From<String> for StringFilter {
119 fn from(v: String) -> Self {
120 Self::equals(v)
121 }
122}
123
124impl ScalarFilter for StringFilter {
125 fn into_filter(self, column: &str) -> Filter {
126 let mut parts: Vec<Filter> = Vec::new();
127 let col = column.to_string();
128 if let Some(v) = self.equals {
129 parts.push(Filter::Equals(col.clone().into(), FilterValue::String(v)));
130 }
131 if let Some(boxed) = self.not {
132 let inner = boxed.into_filter(column);
133 parts.push(Filter::Not(Box::new(inner)));
134 }
135 if let Some(values) = self.in_list {
136 let vs: Vec<FilterValue> = values.into_iter().map(FilterValue::String).collect();
137 parts.push(Filter::In(col.clone().into(), vs));
138 }
139 if let Some(values) = self.not_in {
140 let vs: Vec<FilterValue> = values.into_iter().map(FilterValue::String).collect();
141 parts.push(Filter::NotIn(col.clone().into(), vs));
142 }
143 if let Some(v) = self.lt {
144 parts.push(Filter::Lt(col.clone().into(), FilterValue::String(v)));
145 }
146 if let Some(v) = self.lte {
147 parts.push(Filter::Lte(col.clone().into(), FilterValue::String(v)));
148 }
149 if let Some(v) = self.gt {
150 parts.push(Filter::Gt(col.clone().into(), FilterValue::String(v)));
151 }
152 if let Some(v) = self.gte {
153 parts.push(Filter::Gte(col.clone().into(), FilterValue::String(v)));
154 }
155 if let Some(v) = self.contains {
156 parts.push(Filter::Contains(col.clone().into(), FilterValue::String(v)));
157 }
158 if let Some(v) = self.starts_with {
159 parts.push(Filter::StartsWith(
160 col.clone().into(),
161 FilterValue::String(v),
162 ));
163 }
164 if let Some(v) = self.ends_with {
165 parts.push(Filter::EndsWith(col.clone().into(), FilterValue::String(v)));
166 }
167 let _ = self.mode;
171 combine_filters(parts)
172 }
173}
174
175#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
177#[serde(rename_all = "snake_case")]
178pub struct StringNullableFilter {
179 pub equals: Option<String>,
181 pub not: Option<Box<StringNullableFilter>>,
183 pub in_list: Option<Vec<String>>,
185 pub not_in: Option<Vec<String>>,
187 pub lt: Option<String>,
189 pub lte: Option<String>,
191 pub gt: Option<String>,
193 pub gte: Option<String>,
195 pub contains: Option<String>,
197 pub starts_with: Option<String>,
199 pub ends_with: Option<String>,
201 pub mode: Option<QueryMode>,
203 pub is_null: Option<bool>,
205}
206
207impl From<&str> for StringNullableFilter {
208 fn from(v: &str) -> Self {
209 Self {
210 equals: Some(v.into()),
211 ..Default::default()
212 }
213 }
214}
215impl From<String> for StringNullableFilter {
216 fn from(v: String) -> Self {
217 Self {
218 equals: Some(v),
219 ..Default::default()
220 }
221 }
222}
223
224impl ScalarFilter for StringNullableFilter {
225 fn into_filter(self, column: &str) -> Filter {
226 let mut parts: Vec<Filter> = Vec::new();
227 let col = column.to_string();
228 if let Some(b) = self.is_null {
229 parts.push(if b {
230 Filter::IsNull(col.clone().into())
231 } else {
232 Filter::IsNotNull(col.clone().into())
233 });
234 }
235 let inner = StringFilter {
237 equals: self.equals,
238 not: self.not.map(|b| {
239 Box::new(StringFilter {
240 equals: b.equals,
241 in_list: b.in_list,
242 not_in: b.not_in,
243 lt: b.lt,
244 lte: b.lte,
245 gt: b.gt,
246 gte: b.gte,
247 contains: b.contains,
248 starts_with: b.starts_with,
249 ends_with: b.ends_with,
250 mode: b.mode,
251 not: None,
252 })
253 }),
254 in_list: self.in_list,
255 not_in: self.not_in,
256 lt: self.lt,
257 lte: self.lte,
258 gt: self.gt,
259 gte: self.gte,
260 contains: self.contains,
261 starts_with: self.starts_with,
262 ends_with: self.ends_with,
263 mode: self.mode,
264 };
265 let inner_filter = inner.into_filter(column);
266 if !matches!(inner_filter, Filter::None) {
267 parts.push(inner_filter);
268 }
269 combine_filters(parts)
270 }
271}
272
273macro_rules! scalar_filter {
278 (
279 $(#[$nn_meta:meta])*
280 $name:ident<$rust:ty> => |$conv_v:ident| $conv:block,
281 $(#[$null_meta:meta])*
282 nullable $null:ident
283 ) => {
284 $(#[$nn_meta])*
285 #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
286 #[serde(rename_all = "snake_case")]
287 pub struct $name {
288 pub equals: Option<$rust>,
290 pub not: Option<Box<$name>>,
292 pub in_list: Option<Vec<$rust>>,
294 pub not_in: Option<Vec<$rust>>,
296 pub lt: Option<$rust>,
298 pub lte: Option<$rust>,
300 pub gt: Option<$rust>,
302 pub gte: Option<$rust>,
304 }
305
306 impl $name {
307 pub fn equals(v: impl Into<$rust>) -> Self {
309 Self { equals: Some(v.into()), ..Default::default() }
310 }
311 pub fn lt(v: impl Into<$rust>) -> Self {
313 Self { lt: Some(v.into()), ..Default::default() }
314 }
315 pub fn lte(v: impl Into<$rust>) -> Self {
317 Self { lte: Some(v.into()), ..Default::default() }
318 }
319 pub fn gt(v: impl Into<$rust>) -> Self {
321 Self { gt: Some(v.into()), ..Default::default() }
322 }
323 pub fn gte(v: impl Into<$rust>) -> Self {
325 Self { gte: Some(v.into()), ..Default::default() }
326 }
327 }
328
329 impl ScalarFilter for $name {
330 fn into_filter(self, column: &str) -> Filter {
331 fn to_fv($conv_v: $rust) -> FilterValue $conv
332 let col: crate::filter::FieldName = column.to_string().into();
333 let mut parts: Vec<Filter> = Vec::new();
334 if let Some(v) = self.equals {
335 parts.push(Filter::Equals(col.clone(), to_fv(v)));
336 }
337 if let Some(boxed) = self.not {
338 let inner = boxed.into_filter(column);
339 parts.push(Filter::Not(Box::new(inner)));
340 }
341 if let Some(values) = self.in_list {
342 let vs: Vec<FilterValue> = values.into_iter().map(to_fv).collect();
343 parts.push(Filter::In(col.clone(), vs));
344 }
345 if let Some(values) = self.not_in {
346 let vs: Vec<FilterValue> = values.into_iter().map(to_fv).collect();
347 parts.push(Filter::NotIn(col.clone(), vs));
348 }
349 if let Some(v) = self.lt { parts.push(Filter::Lt(col.clone(), to_fv(v))); }
350 if let Some(v) = self.lte { parts.push(Filter::Lte(col.clone(), to_fv(v))); }
351 if let Some(v) = self.gt { parts.push(Filter::Gt(col.clone(), to_fv(v))); }
352 if let Some(v) = self.gte { parts.push(Filter::Gte(col, to_fv(v))); }
353 combine_filters(parts)
354 }
355 }
356
357 $(#[$null_meta])*
358 #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
359 #[serde(rename_all = "snake_case")]
360 pub struct $null {
361 pub equals: Option<$rust>,
363 pub not: Option<Box<$null>>,
365 pub in_list: Option<Vec<$rust>>,
367 pub not_in: Option<Vec<$rust>>,
369 pub lt: Option<$rust>,
371 pub lte: Option<$rust>,
373 pub gt: Option<$rust>,
375 pub gte: Option<$rust>,
377 pub is_null: Option<bool>,
379 }
380
381 impl ScalarFilter for $null {
382 fn into_filter(self, column: &str) -> Filter {
383 let mut parts: Vec<Filter> = Vec::new();
384 if let Some(b) = self.is_null {
385 parts.push(if b {
386 Filter::IsNull(column.to_string().into())
387 } else {
388 Filter::IsNotNull(column.to_string().into())
389 });
390 }
391 let inner = $name {
392 equals: self.equals,
393 not: self.not.map(|b| Box::new($name {
394 equals: b.equals,
395 in_list: b.in_list,
396 not_in: b.not_in,
397 lt: b.lt, lte: b.lte, gt: b.gt, gte: b.gte,
398 not: None,
399 })),
400 in_list: self.in_list,
401 not_in: self.not_in,
402 lt: self.lt, lte: self.lte, gt: self.gt, gte: self.gte,
403 };
404 let f = inner.into_filter(column);
405 if !matches!(f, Filter::None) { parts.push(f); }
406 combine_filters(parts)
407 }
408 }
409 };
410}
411
412scalar_filter!(
413 IntFilter<i32> => |v| { FilterValue::Int(v as i64) },
415 nullable IntNullableFilter
417);
418
419scalar_filter!(
420 BigIntFilter<i64> => |v| { FilterValue::Int(v) },
422 nullable BigIntNullableFilter
424);
425
426scalar_filter!(
427 FloatFilter<f64> => |v| { FilterValue::Float(v) },
429 nullable FloatNullableFilter
431);
432
433scalar_filter!(
434 DecimalFilter<rust_decimal::Decimal> => |v| { FilterValue::String(v.to_string()) },
440 nullable DecimalNullableFilter
442);
443
444scalar_filter!(
445 UuidFilter<uuid::Uuid> => |v| { FilterValue::String(v.to_string()) },
447 nullable UuidNullableFilter
449);
450
451scalar_filter!(
452 BytesFilter<Vec<u8>> => |v| {
457 use base64::Engine as _;
458 FilterValue::String(base64::engine::general_purpose::STANDARD.encode(&v))
459 },
460 nullable BytesNullableFilter
462);
463
464#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
466#[serde(rename_all = "snake_case")]
467pub struct BoolFilter {
468 pub equals: Option<bool>,
470 pub not: Option<Box<BoolFilter>>,
472}
473
474impl BoolFilter {
475 pub fn equals(v: bool) -> Self {
477 Self {
478 equals: Some(v),
479 ..Default::default()
480 }
481 }
482}
483
484impl From<bool> for BoolFilter {
485 fn from(v: bool) -> Self {
486 Self::equals(v)
487 }
488}
489
490impl ScalarFilter for BoolFilter {
491 fn into_filter(self, column: &str) -> Filter {
492 let col: crate::filter::FieldName = column.to_string().into();
493 let mut parts: Vec<Filter> = Vec::new();
494 if let Some(v) = self.equals {
495 parts.push(Filter::Equals(col.clone(), FilterValue::Bool(v)));
496 }
497 if let Some(boxed) = self.not {
498 parts.push(Filter::Not(Box::new(boxed.into_filter(column))));
499 }
500 combine_filters(parts)
501 }
502}
503
504#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
506#[serde(rename_all = "snake_case")]
507pub struct BoolNullableFilter {
508 pub equals: Option<bool>,
510 pub not: Option<Box<BoolNullableFilter>>,
512 pub is_null: Option<bool>,
514}
515
516impl ScalarFilter for BoolNullableFilter {
517 fn into_filter(self, column: &str) -> Filter {
518 let mut parts: Vec<Filter> = Vec::new();
519 if let Some(b) = self.is_null {
520 parts.push(if b {
521 Filter::IsNull(column.to_string().into())
522 } else {
523 Filter::IsNotNull(column.to_string().into())
524 });
525 }
526 let inner = BoolFilter {
527 equals: self.equals,
528 not: self.not.map(|b| {
529 Box::new(BoolFilter {
530 equals: b.equals,
531 not: None,
532 })
533 }),
534 };
535 let f = inner.into_filter(column);
536 if !matches!(f, Filter::None) {
537 parts.push(f);
538 }
539 combine_filters(parts)
540 }
541}
542
543scalar_filter!(
544 DateTimeFilter<chrono::DateTime<chrono::Utc>> => |v| {
546 FilterValue::String(v.to_rfc3339())
547 },
548 nullable DateTimeNullableFilter
550);
551
552scalar_filter!(
553 DateFilter<chrono::NaiveDate> => |v| {
555 FilterValue::String(v.to_string())
556 },
557 nullable DateNullableFilter
559);
560
561scalar_filter!(
562 TimeFilter<chrono::NaiveTime> => |v| {
564 FilterValue::String(v.format("%H:%M:%S").to_string())
565 },
566 nullable TimeNullableFilter
568);
569
570#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
575#[serde(
576 rename_all = "snake_case",
577 bound = "E: Serialize + for<'de2> Deserialize<'de2>"
578)]
579pub struct EnumFilter<E> {
580 pub equals: Option<E>,
582 pub not: Option<Box<EnumFilter<E>>>,
584 pub in_list: Option<Vec<E>>,
586 pub not_in: Option<Vec<E>>,
588}
589
590impl<E> EnumFilter<E> {
591 pub fn equals(v: E) -> Self {
593 Self {
594 equals: Some(v),
595 not: None,
596 in_list: None,
597 not_in: None,
598 }
599 }
600}
601
602impl<E: ToString> ScalarFilter for EnumFilter<E> {
603 fn into_filter(self, column: &str) -> Filter {
604 let col: crate::filter::FieldName = column.to_string().into();
605 let mut parts: Vec<Filter> = Vec::new();
606 if let Some(v) = self.equals {
607 parts.push(Filter::Equals(
608 col.clone(),
609 FilterValue::String(v.to_string()),
610 ));
611 }
612 if let Some(boxed) = self.not {
613 parts.push(Filter::Not(Box::new(boxed.into_filter(column))));
614 }
615 if let Some(values) = self.in_list {
616 let vs: Vec<FilterValue> = values
617 .into_iter()
618 .map(|v| FilterValue::String(v.to_string()))
619 .collect();
620 parts.push(Filter::In(col.clone(), vs));
621 }
622 if let Some(values) = self.not_in {
623 let vs: Vec<FilterValue> = values
624 .into_iter()
625 .map(|v| FilterValue::String(v.to_string()))
626 .collect();
627 parts.push(Filter::NotIn(col, vs));
628 }
629 combine_filters(parts)
630 }
631}
632
633#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
635#[serde(
636 rename_all = "snake_case",
637 bound = "E: Serialize + for<'de2> Deserialize<'de2>"
638)]
639pub struct EnumNullableFilter<E> {
640 pub equals: Option<E>,
642 pub not: Option<Box<EnumNullableFilter<E>>>,
644 pub in_list: Option<Vec<E>>,
646 pub not_in: Option<Vec<E>>,
648 pub is_null: Option<bool>,
650}
651
652impl<E: ToString> ScalarFilter for EnumNullableFilter<E> {
653 fn into_filter(self, column: &str) -> Filter {
654 let mut parts: Vec<Filter> = Vec::new();
655 if let Some(b) = self.is_null {
656 parts.push(if b {
657 Filter::IsNull(column.to_string().into())
658 } else {
659 Filter::IsNotNull(column.to_string().into())
660 });
661 }
662 let inner = EnumFilter::<E> {
663 equals: self.equals,
664 not: self.not.map(|b| {
665 Box::new(EnumFilter {
666 equals: b.equals,
667 in_list: b.in_list,
668 not_in: b.not_in,
669 not: None,
670 })
671 }),
672 in_list: self.in_list,
673 not_in: self.not_in,
674 };
675 let f = inner.into_filter(column);
676 if !matches!(f, Filter::None) {
677 parts.push(f);
678 }
679 combine_filters(parts)
680 }
681}
682
683#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
688#[serde(rename_all = "snake_case")]
689pub struct JsonFilter {
690 pub equals: Option<serde_json::Value>,
692 pub not: Option<Box<JsonFilter>>,
694}
695
696impl ScalarFilter for JsonFilter {
697 fn into_filter(self, column: &str) -> Filter {
698 let col: crate::filter::FieldName = column.to_string().into();
699 let mut parts: Vec<Filter> = Vec::new();
700 if let Some(v) = self.equals {
701 parts.push(Filter::Equals(col.clone(), FilterValue::Json(v)));
702 }
703 if let Some(boxed) = self.not {
704 parts.push(Filter::Not(Box::new(boxed.into_filter(column))));
705 }
706 combine_filters(parts)
707 }
708}
709
710#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
712#[serde(rename_all = "snake_case")]
713pub struct JsonNullableFilter {
714 pub equals: Option<serde_json::Value>,
716 pub not: Option<Box<JsonNullableFilter>>,
718 pub is_null: Option<bool>,
720}
721
722impl ScalarFilter for JsonNullableFilter {
723 fn into_filter(self, column: &str) -> Filter {
724 let mut parts: Vec<Filter> = Vec::new();
725 if let Some(b) = self.is_null {
726 parts.push(if b {
727 Filter::IsNull(column.to_string().into())
728 } else {
729 Filter::IsNotNull(column.to_string().into())
730 });
731 }
732 let inner = JsonFilter {
733 equals: self.equals,
734 not: self.not.map(|b| {
735 Box::new(JsonFilter {
736 equals: b.equals,
737 not: None,
738 })
739 }),
740 };
741 let f = inner.into_filter(column);
742 if !matches!(f, Filter::None) {
743 parts.push(f);
744 }
745 combine_filters(parts)
746 }
747}