Skip to main content

shelly_data/
query.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "snake_case")]
6pub enum FilterOperator {
7    Eq,
8    Neq,
9    Gt,
10    Gte,
11    Lt,
12    Lte,
13    Contains,
14}
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct Filter {
18    pub field: String,
19    pub op: FilterOperator,
20    pub value: Value,
21}
22
23impl Filter {
24    pub fn eq(field: impl Into<String>, value: Value) -> Self {
25        Self {
26            field: field.into(),
27            op: FilterOperator::Eq,
28            value,
29        }
30    }
31
32    pub fn contains(field: impl Into<String>, value: impl Into<String>) -> Self {
33        Self {
34            field: field.into(),
35            op: FilterOperator::Contains,
36            value: Value::String(value.into()),
37        }
38    }
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "snake_case")]
43pub enum SortDirection {
44    Asc,
45    Desc,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
49pub struct Sort {
50    pub field: String,
51    pub direction: SortDirection,
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
55pub struct Pagination {
56    pub page: usize,
57    pub per_page: usize,
58}
59
60impl Pagination {
61    pub fn new(page: usize, per_page: usize) -> Self {
62        Self {
63            page: page.max(1),
64            per_page: per_page.max(1),
65        }
66    }
67}
68
69#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
70pub struct Query {
71    pub filters: Vec<Filter>,
72    pub sorts: Vec<Sort>,
73    pub pagination: Option<Pagination>,
74    pub preloads: Vec<String>,
75}
76
77impl Query {
78    pub fn new() -> Self {
79        Self::default()
80    }
81
82    pub fn where_filter(mut self, filter: Filter) -> Self {
83        self.filters.push(filter);
84        self
85    }
86
87    pub fn order_by(mut self, field: impl Into<String>, direction: SortDirection) -> Self {
88        self.sorts.push(Sort {
89            field: field.into(),
90            direction,
91        });
92        self
93    }
94
95    pub fn paginate(mut self, page: usize, per_page: usize) -> Self {
96        self.pagination = Some(Pagination::new(page, per_page));
97        self
98    }
99
100    pub fn preload(mut self, relation: impl Into<String>) -> Self {
101        self.preloads.push(relation.into());
102        self
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::{Filter, FilterOperator, Pagination, Query, SortDirection};
109    use serde_json::json;
110
111    #[test]
112    fn pagination_clamps_zero_values_to_one() {
113        let pagination = Pagination::new(0, 0);
114        assert_eq!(pagination.page, 1);
115        assert_eq!(pagination.per_page, 1);
116    }
117
118    #[test]
119    fn filter_builders_create_expected_filters() {
120        assert_eq!(
121            Filter::eq("title", json!("Hello")),
122            Filter {
123                field: "title".to_string(),
124                op: FilterOperator::Eq,
125                value: json!("Hello"),
126            }
127        );
128        assert_eq!(
129            Filter::contains("body", "rust"),
130            Filter {
131                field: "body".to_string(),
132                op: FilterOperator::Contains,
133                value: json!("rust"),
134            }
135        );
136    }
137
138    #[test]
139    fn query_builder_collects_filters_sorts_pagination_and_preloads() {
140        let query = Query::new()
141            .where_filter(Filter::eq("id", json!(10)))
142            .where_filter(Filter::contains("title", "post"))
143            .order_by("inserted_at", SortDirection::Desc)
144            .order_by("title", SortDirection::Asc)
145            .paginate(0, 50)
146            .preload("author")
147            .preload("comments");
148
149        assert_eq!(query.filters.len(), 2);
150        assert_eq!(query.sorts.len(), 2);
151        assert_eq!(
152            query.pagination,
153            Some(Pagination {
154                page: 1,
155                per_page: 50
156            })
157        );
158        assert_eq!(
159            query.preloads,
160            vec!["author".to_string(), "comments".to_string()]
161        );
162    }
163}