1use std::fmt;
2
3use serde::Deserialize;
4
5#[derive(Default, Deserialize, Debug, PartialEq)]
6pub struct QueryFilter<T> {
7 pub start: Option<i32>,
8 pub end: Option<i32>,
9 pub sort: Option<String>,
10 pub order: Option<String>,
11
12 pub filter: Option<T>,
13}
14
15impl<T> QueryFilter<T>
16where
17 T: Filter,
18{
19 pub fn get_offset(&self) -> i32 {
20 if let Some(offset) = self.start {
22 offset
23 } else {
24 0
25 }
26 }
27
28 pub fn get_limit(&self, offset: i32) -> i32 {
29 if let Some(end) = self.end {
30 std::cmp::max(end - offset, 1)
31 } else {
32 10
33 }
34 }
35
36 pub fn get_sort(&self) -> Option<&'static str> {
37 if let Some(ref field) = self.sort {
38 return T::validate_sortable_field(field);
39 } else {
40 None
41 }
42 }
43
44 pub fn get_order(&self) -> Order {
45 if let Some(ref val) = self.order {
46 val.parse().unwrap_or(Order::None)
47 } else {
48 Order::None
49 }
50 }
51
52 pub fn get_filter(&self) -> Option<&T> {
53 self.filter.as_ref()
54 }
55}
56
57pub trait Filter {
58 const SORTABLE_FIELDS: &'static [&'static str];
59
60 fn validate_sortable_field(field: &str) -> Option<&'static str> {
61 for f in Self::SORTABLE_FIELDS.iter() {
62 if f == &field {
63 return Some(f);
64 }
65 }
66 if Self::SORTABLE_FIELDS.iter().any(|f| f == &field) {}
67 None
68 }
69
70 fn get_max_limit() -> i32 {
71 100
72 }
73}
74
75pub enum Order {
76 Asc,
77 Desc,
78 None,
79}
80
81impl Order {
82 pub fn as_str(&self) -> &str {
83 match self {
84 Self::Desc => "DESC",
85 _ => "ASC",
86 }
87 }
88}
89
90impl std::fmt::Display for Order {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 f.write_str(self.as_str())
93 }
94}
95
96impl std::str::FromStr for Order {
97 type Err = ();
98 fn from_str(val: &str) -> Result<Self, Self::Err> {
99 match val.to_ascii_uppercase().as_str() {
100 "ASC" => Ok(Order::Asc),
101 "DESC" => Ok(Order::Desc),
102 _ => Err(()),
103 }
104 }
105}
106
107#[cfg(feature = "openapi")]
108mod openapi {
109 use utoipa::{
110 openapi::{
111 path::{Parameter, ParameterBuilder, ParameterIn, ParameterStyle},
112 KnownFormat, ObjectBuilder, RefOr, Required, Schema, SchemaFormat, SchemaType,
113 },
114 IntoParams, ToSchema,
115 };
116
117 use crate::QueryFilter;
118
119 impl<T> ToSchema<'static> for QueryFilter<T>
120 where
121 T: ToSchema<'static>,
122 {
123 fn schema() -> (&'static str, RefOr<Schema>) {
124 (
125 T::schema().0,
127 ObjectBuilder::new()
128 .property(
129 "start",
130 ObjectBuilder::new()
131 .schema_type(SchemaType::Integer)
132 .format(Some(SchemaFormat::KnownFormat(KnownFormat::Int32))),
133 )
134 .property(
135 "end",
136 ObjectBuilder::new()
137 .schema_type(SchemaType::Integer)
138 .format(Some(SchemaFormat::KnownFormat(KnownFormat::Int32))),
139 )
140 .property("sort", ObjectBuilder::new().schema_type(SchemaType::String))
141 .property(
142 "order",
143 ObjectBuilder::new().schema_type(SchemaType::String),
144 )
145 .property("filter", T::schema().1)
146 .into(),
147 )
148 }
149 }
150
151 impl<T> IntoParams for QueryFilter<T>
152 where
153 T: ToSchema<'static>,
154 {
155 fn into_params(_: impl Fn() -> Option<ParameterIn>) -> Vec<Parameter> {
156 [
157 ParameterBuilder::new()
158 .name("start")
159 .parameter_in(ParameterIn::Query)
160 .style(Some(ParameterStyle::Form))
161 .required(Required::False)
162 .schema(Some(
163 ObjectBuilder::new()
164 .schema_type(SchemaType::Integer)
165 .format(Some(SchemaFormat::KnownFormat(KnownFormat::Int32))),
166 ))
167 .build(),
168 ParameterBuilder::new()
169 .name("end")
170 .parameter_in(ParameterIn::Query)
171 .style(Some(ParameterStyle::Form))
172 .required(Required::False)
173 .schema(Some(
174 ObjectBuilder::new()
175 .schema_type(SchemaType::Integer)
176 .format(Some(SchemaFormat::KnownFormat(KnownFormat::Int32))),
177 ))
178 .build(),
179 ParameterBuilder::new()
180 .name("sort")
181 .parameter_in(ParameterIn::Query)
182 .style(Some(ParameterStyle::Form))
183 .required(Required::False)
184 .schema(Some(ObjectBuilder::new().schema_type(SchemaType::String)))
185 .build(),
186 ParameterBuilder::new()
187 .name("order")
188 .parameter_in(ParameterIn::Query)
189 .style(Some(ParameterStyle::Form))
190 .required(Required::False)
191 .schema(Some(
192 ObjectBuilder::new()
193 .schema_type(SchemaType::String)
194 .enum_values(Some(["ASC", "DESC"])),
195 ))
196 .build(),
197 ParameterBuilder::new()
198 .name("filter")
199 .parameter_in(ParameterIn::Query)
200 .style(Some(ParameterStyle::DeepObject))
201 .explode(Some(true))
202 .required(Required::False)
203 .schema(Some(T::schema().1))
204 .build(),
205 ]
206 .to_vec()
207 }
208 }
209}
210
211#[cfg(feature = "seaq")]
212mod seaq {
213 use super::Order;
214
215 impl Order {
216 pub fn to_seaquery(&self) -> sea_query::Order {
217 match self {
218 Self::Desc => sea_query::Order::Desc,
219 _ => sea_query::Order::Asc,
220 }
221 }
222 }
223}