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}