use crate::filter::{Filter, FilterValue};
use serde::{Deserialize, Serialize};
pub trait ScalarFilter {
fn into_filter(self, column: &str) -> Filter;
}
pub(crate) fn combine_filters(parts: Vec<Filter>) -> Filter {
match parts.len() {
0 => Filter::None,
1 => parts.into_iter().next().unwrap(),
_ => Filter::and(parts),
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum QueryMode {
#[default]
Default,
Insensitive,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct StringFilter {
pub equals: Option<String>,
pub not: Option<Box<StringFilter>>,
pub in_list: Option<Vec<String>>,
pub not_in: Option<Vec<String>>,
pub lt: Option<String>,
pub lte: Option<String>,
pub gt: Option<String>,
pub gte: Option<String>,
pub contains: Option<String>,
pub starts_with: Option<String>,
pub ends_with: Option<String>,
pub mode: Option<QueryMode>,
}
impl StringFilter {
pub fn equals(v: impl Into<String>) -> Self {
Self {
equals: Some(v.into()),
..Default::default()
}
}
pub fn contains(v: impl Into<String>) -> Self {
Self {
contains: Some(v.into()),
..Default::default()
}
}
pub fn starts_with(v: impl Into<String>) -> Self {
Self {
starts_with: Some(v.into()),
..Default::default()
}
}
pub fn ends_with(v: impl Into<String>) -> Self {
Self {
ends_with: Some(v.into()),
..Default::default()
}
}
}
impl From<&str> for StringFilter {
fn from(v: &str) -> Self {
Self::equals(v)
}
}
impl From<String> for StringFilter {
fn from(v: String) -> Self {
Self::equals(v)
}
}
impl ScalarFilter for StringFilter {
fn into_filter(self, column: &str) -> Filter {
let mut parts: Vec<Filter> = Vec::new();
let col = column.to_string();
if let Some(v) = self.equals {
parts.push(Filter::Equals(col.clone().into(), FilterValue::String(v)));
}
if let Some(boxed) = self.not {
let inner = boxed.into_filter(column);
parts.push(Filter::Not(Box::new(inner)));
}
if let Some(values) = self.in_list {
let vs: Vec<FilterValue> = values.into_iter().map(FilterValue::String).collect();
parts.push(Filter::In(col.clone().into(), vs));
}
if let Some(values) = self.not_in {
let vs: Vec<FilterValue> = values.into_iter().map(FilterValue::String).collect();
parts.push(Filter::NotIn(col.clone().into(), vs));
}
if let Some(v) = self.lt {
parts.push(Filter::Lt(col.clone().into(), FilterValue::String(v)));
}
if let Some(v) = self.lte {
parts.push(Filter::Lte(col.clone().into(), FilterValue::String(v)));
}
if let Some(v) = self.gt {
parts.push(Filter::Gt(col.clone().into(), FilterValue::String(v)));
}
if let Some(v) = self.gte {
parts.push(Filter::Gte(col.clone().into(), FilterValue::String(v)));
}
if let Some(v) = self.contains {
parts.push(Filter::Contains(col.clone().into(), FilterValue::String(v)));
}
if let Some(v) = self.starts_with {
parts.push(Filter::StartsWith(
col.clone().into(),
FilterValue::String(v),
));
}
if let Some(v) = self.ends_with {
parts.push(Filter::EndsWith(col.clone().into(), FilterValue::String(v)));
}
let _ = self.mode;
combine_filters(parts)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct StringNullableFilter {
pub equals: Option<String>,
pub not: Option<Box<StringNullableFilter>>,
pub in_list: Option<Vec<String>>,
pub not_in: Option<Vec<String>>,
pub lt: Option<String>,
pub lte: Option<String>,
pub gt: Option<String>,
pub gte: Option<String>,
pub contains: Option<String>,
pub starts_with: Option<String>,
pub ends_with: Option<String>,
pub mode: Option<QueryMode>,
pub is_null: Option<bool>,
}
impl From<&str> for StringNullableFilter {
fn from(v: &str) -> Self {
Self {
equals: Some(v.into()),
..Default::default()
}
}
}
impl From<String> for StringNullableFilter {
fn from(v: String) -> Self {
Self {
equals: Some(v),
..Default::default()
}
}
}
impl ScalarFilter for StringNullableFilter {
fn into_filter(self, column: &str) -> Filter {
let mut parts: Vec<Filter> = Vec::new();
let col = column.to_string();
if let Some(b) = self.is_null {
parts.push(if b {
Filter::IsNull(col.clone().into())
} else {
Filter::IsNotNull(col.clone().into())
});
}
let inner = StringFilter {
equals: self.equals,
not: self.not.map(|b| {
Box::new(StringFilter {
equals: b.equals,
in_list: b.in_list,
not_in: b.not_in,
lt: b.lt,
lte: b.lte,
gt: b.gt,
gte: b.gte,
contains: b.contains,
starts_with: b.starts_with,
ends_with: b.ends_with,
mode: b.mode,
not: None,
})
}),
in_list: self.in_list,
not_in: self.not_in,
lt: self.lt,
lte: self.lte,
gt: self.gt,
gte: self.gte,
contains: self.contains,
starts_with: self.starts_with,
ends_with: self.ends_with,
mode: self.mode,
};
let inner_filter = inner.into_filter(column);
if !matches!(inner_filter, Filter::None) {
parts.push(inner_filter);
}
combine_filters(parts)
}
}
macro_rules! scalar_filter {
(
$(#[$nn_meta:meta])*
$name:ident<$rust:ty> => |$conv_v:ident| $conv:block,
$(#[$null_meta:meta])*
nullable $null:ident
) => {
$(#[$nn_meta])*
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct $name {
/// `column = value`
pub equals: Option<$rust>,
pub not: Option<Box<$name>>,
pub in_list: Option<Vec<$rust>>,
pub not_in: Option<Vec<$rust>>,
pub lt: Option<$rust>,
pub lte: Option<$rust>,
pub gt: Option<$rust>,
pub gte: Option<$rust>,
}
impl $name {
pub fn equals(v: impl Into<$rust>) -> Self {
Self { equals: Some(v.into()), ..Default::default() }
}
pub fn lt(v: impl Into<$rust>) -> Self {
Self { lt: Some(v.into()), ..Default::default() }
}
pub fn lte(v: impl Into<$rust>) -> Self {
Self { lte: Some(v.into()), ..Default::default() }
}
pub fn gt(v: impl Into<$rust>) -> Self {
Self { gt: Some(v.into()), ..Default::default() }
}
pub fn gte(v: impl Into<$rust>) -> Self {
Self { gte: Some(v.into()), ..Default::default() }
}
}
impl ScalarFilter for $name {
fn into_filter(self, column: &str) -> Filter {
fn to_fv($conv_v: $rust) -> FilterValue $conv
let col: crate::filter::FieldName = column.to_string().into();
let mut parts: Vec<Filter> = Vec::new();
if let Some(v) = self.equals {
parts.push(Filter::Equals(col.clone(), to_fv(v)));
}
if let Some(boxed) = self.not {
let inner = boxed.into_filter(column);
parts.push(Filter::Not(Box::new(inner)));
}
if let Some(values) = self.in_list {
let vs: Vec<FilterValue> = values.into_iter().map(to_fv).collect();
parts.push(Filter::In(col.clone(), vs));
}
if let Some(values) = self.not_in {
let vs: Vec<FilterValue> = values.into_iter().map(to_fv).collect();
parts.push(Filter::NotIn(col.clone(), vs));
}
if let Some(v) = self.lt { parts.push(Filter::Lt(col.clone(), to_fv(v))); }
if let Some(v) = self.lte { parts.push(Filter::Lte(col.clone(), to_fv(v))); }
if let Some(v) = self.gt { parts.push(Filter::Gt(col.clone(), to_fv(v))); }
if let Some(v) = self.gte { parts.push(Filter::Gte(col, to_fv(v))); }
combine_filters(parts)
}
}
$(#[$null_meta])*
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct $null {
/// `column = value`
pub equals: Option<$rust>,
pub not: Option<Box<$null>>,
pub in_list: Option<Vec<$rust>>,
pub not_in: Option<Vec<$rust>>,
pub lt: Option<$rust>,
pub lte: Option<$rust>,
pub gt: Option<$rust>,
pub gte: Option<$rust>,
pub is_null: Option<bool>,
}
impl ScalarFilter for $null {
fn into_filter(self, column: &str) -> Filter {
let mut parts: Vec<Filter> = Vec::new();
if let Some(b) = self.is_null {
parts.push(if b {
Filter::IsNull(column.to_string().into())
} else {
Filter::IsNotNull(column.to_string().into())
});
}
let inner = $name {
equals: self.equals,
not: self.not.map(|b| Box::new($name {
equals: b.equals,
in_list: b.in_list,
not_in: b.not_in,
lt: b.lt, lte: b.lte, gt: b.gt, gte: b.gte,
not: None,
})),
in_list: self.in_list,
not_in: self.not_in,
lt: self.lt, lte: self.lte, gt: self.gt, gte: self.gte,
};
let f = inner.into_filter(column);
if !matches!(f, Filter::None) { parts.push(f); }
combine_filters(parts)
}
}
};
}
scalar_filter!(
IntFilter<i32> => |v| { FilterValue::Int(v as i64) },
nullable IntNullableFilter
);
scalar_filter!(
BigIntFilter<i64> => |v| { FilterValue::Int(v) },
nullable BigIntNullableFilter
);
scalar_filter!(
FloatFilter<f64> => |v| { FilterValue::Float(v) },
nullable FloatNullableFilter
);
scalar_filter!(
DecimalFilter<rust_decimal::Decimal> => |v| { FilterValue::String(v.to_string()) },
nullable DecimalNullableFilter
);
scalar_filter!(
UuidFilter<uuid::Uuid> => |v| { FilterValue::String(v.to_string()) },
nullable UuidNullableFilter
);
scalar_filter!(
BytesFilter<Vec<u8>> => |v| {
use base64::Engine as _;
FilterValue::String(base64::engine::general_purpose::STANDARD.encode(&v))
},
nullable BytesNullableFilter
);
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct BoolFilter {
pub equals: Option<bool>,
pub not: Option<Box<BoolFilter>>,
}
impl BoolFilter {
pub fn equals(v: bool) -> Self {
Self {
equals: Some(v),
..Default::default()
}
}
}
impl From<bool> for BoolFilter {
fn from(v: bool) -> Self {
Self::equals(v)
}
}
impl ScalarFilter for BoolFilter {
fn into_filter(self, column: &str) -> Filter {
let col: crate::filter::FieldName = column.to_string().into();
let mut parts: Vec<Filter> = Vec::new();
if let Some(v) = self.equals {
parts.push(Filter::Equals(col.clone(), FilterValue::Bool(v)));
}
if let Some(boxed) = self.not {
parts.push(Filter::Not(Box::new(boxed.into_filter(column))));
}
combine_filters(parts)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct BoolNullableFilter {
pub equals: Option<bool>,
pub not: Option<Box<BoolNullableFilter>>,
pub is_null: Option<bool>,
}
impl ScalarFilter for BoolNullableFilter {
fn into_filter(self, column: &str) -> Filter {
let mut parts: Vec<Filter> = Vec::new();
if let Some(b) = self.is_null {
parts.push(if b {
Filter::IsNull(column.to_string().into())
} else {
Filter::IsNotNull(column.to_string().into())
});
}
let inner = BoolFilter {
equals: self.equals,
not: self.not.map(|b| {
Box::new(BoolFilter {
equals: b.equals,
not: None,
})
}),
};
let f = inner.into_filter(column);
if !matches!(f, Filter::None) {
parts.push(f);
}
combine_filters(parts)
}
}
scalar_filter!(
DateTimeFilter<chrono::DateTime<chrono::Utc>> => |v| {
FilterValue::String(v.to_rfc3339())
},
nullable DateTimeNullableFilter
);
scalar_filter!(
DateFilter<chrono::NaiveDate> => |v| {
FilterValue::String(v.to_string())
},
nullable DateNullableFilter
);
scalar_filter!(
TimeFilter<chrono::NaiveTime> => |v| {
FilterValue::String(v.format("%H:%M:%S").to_string())
},
nullable TimeNullableFilter
);
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(
rename_all = "snake_case",
bound = "E: Serialize + for<'de2> Deserialize<'de2>"
)]
pub struct EnumFilter<E> {
pub equals: Option<E>,
pub not: Option<Box<EnumFilter<E>>>,
pub in_list: Option<Vec<E>>,
pub not_in: Option<Vec<E>>,
}
impl<E> EnumFilter<E> {
pub fn equals(v: E) -> Self {
Self {
equals: Some(v),
not: None,
in_list: None,
not_in: None,
}
}
}
impl<E: ToString> ScalarFilter for EnumFilter<E> {
fn into_filter(self, column: &str) -> Filter {
let col: crate::filter::FieldName = column.to_string().into();
let mut parts: Vec<Filter> = Vec::new();
if let Some(v) = self.equals {
parts.push(Filter::Equals(
col.clone(),
FilterValue::String(v.to_string()),
));
}
if let Some(boxed) = self.not {
parts.push(Filter::Not(Box::new(boxed.into_filter(column))));
}
if let Some(values) = self.in_list {
let vs: Vec<FilterValue> = values
.into_iter()
.map(|v| FilterValue::String(v.to_string()))
.collect();
parts.push(Filter::In(col.clone(), vs));
}
if let Some(values) = self.not_in {
let vs: Vec<FilterValue> = values
.into_iter()
.map(|v| FilterValue::String(v.to_string()))
.collect();
parts.push(Filter::NotIn(col, vs));
}
combine_filters(parts)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(
rename_all = "snake_case",
bound = "E: Serialize + for<'de2> Deserialize<'de2>"
)]
pub struct EnumNullableFilter<E> {
pub equals: Option<E>,
pub not: Option<Box<EnumNullableFilter<E>>>,
pub in_list: Option<Vec<E>>,
pub not_in: Option<Vec<E>>,
pub is_null: Option<bool>,
}
impl<E: ToString> ScalarFilter for EnumNullableFilter<E> {
fn into_filter(self, column: &str) -> Filter {
let mut parts: Vec<Filter> = Vec::new();
if let Some(b) = self.is_null {
parts.push(if b {
Filter::IsNull(column.to_string().into())
} else {
Filter::IsNotNull(column.to_string().into())
});
}
let inner = EnumFilter::<E> {
equals: self.equals,
not: self.not.map(|b| {
Box::new(EnumFilter {
equals: b.equals,
in_list: b.in_list,
not_in: b.not_in,
not: None,
})
}),
in_list: self.in_list,
not_in: self.not_in,
};
let f = inner.into_filter(column);
if !matches!(f, Filter::None) {
parts.push(f);
}
combine_filters(parts)
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct JsonFilter {
pub equals: Option<serde_json::Value>,
pub not: Option<Box<JsonFilter>>,
}
impl ScalarFilter for JsonFilter {
fn into_filter(self, column: &str) -> Filter {
let col: crate::filter::FieldName = column.to_string().into();
let mut parts: Vec<Filter> = Vec::new();
if let Some(v) = self.equals {
parts.push(Filter::Equals(col.clone(), FilterValue::Json(v)));
}
if let Some(boxed) = self.not {
parts.push(Filter::Not(Box::new(boxed.into_filter(column))));
}
combine_filters(parts)
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub struct JsonNullableFilter {
pub equals: Option<serde_json::Value>,
pub not: Option<Box<JsonNullableFilter>>,
pub is_null: Option<bool>,
}
impl ScalarFilter for JsonNullableFilter {
fn into_filter(self, column: &str) -> Filter {
let mut parts: Vec<Filter> = Vec::new();
if let Some(b) = self.is_null {
parts.push(if b {
Filter::IsNull(column.to_string().into())
} else {
Filter::IsNotNull(column.to_string().into())
});
}
let inner = JsonFilter {
equals: self.equals,
not: self.not.map(|b| {
Box::new(JsonFilter {
equals: b.equals,
not: None,
})
}),
};
let f = inner.into_filter(column);
if !matches!(f, Filter::None) {
parts.push(f);
}
combine_filters(parts)
}
}