seaqs/
query.rs

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        // Check if start is more than 0
21        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                // TODO: maybe we shouldn't use the internal schema's name
126                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}