1use postrust_core::api_request::{
6 Field, OrderDirection as CoreOrderDirection, OrderNulls, OrderTerm,
7};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
12pub enum OrderDirection {
13 #[default]
15 Asc,
16 Desc,
18}
19
20impl From<OrderDirection> for CoreOrderDirection {
21 fn from(dir: OrderDirection) -> Self {
22 match dir {
23 OrderDirection::Asc => CoreOrderDirection::Asc,
24 OrderDirection::Desc => CoreOrderDirection::Desc,
25 }
26 }
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
31pub enum NullsOrder {
32 First,
34 Last,
36}
37
38impl From<NullsOrder> for OrderNulls {
39 fn from(nulls: NullsOrder) -> Self {
40 match nulls {
41 NullsOrder::First => OrderNulls::First,
42 NullsOrder::Last => OrderNulls::Last,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct OrderByField {
50 pub field: String,
52 pub direction: OrderDirection,
54 pub nulls: Option<NullsOrder>,
56}
57
58impl OrderByField {
59 pub fn asc(field: impl Into<String>) -> Self {
61 Self {
62 field: field.into(),
63 direction: OrderDirection::Asc,
64 nulls: None,
65 }
66 }
67
68 pub fn desc(field: impl Into<String>) -> Self {
70 Self {
71 field: field.into(),
72 direction: OrderDirection::Desc,
73 nulls: None,
74 }
75 }
76
77 pub fn with_nulls(mut self, nulls: NullsOrder) -> Self {
79 self.nulls = Some(nulls);
80 self
81 }
82
83 pub fn to_order_term(&self) -> OrderTerm {
85 OrderTerm::Field {
86 field: Field::simple(&self.field),
87 direction: Some(self.direction.into()),
88 nulls: self.nulls.map(|n| n.into()),
89 }
90 }
91}
92
93#[derive(Debug, Clone, Default, Serialize, Deserialize)]
95pub struct PaginationInput {
96 pub limit: Option<i64>,
98 pub offset: Option<i64>,
100}
101
102impl PaginationInput {
103 pub fn new(limit: Option<i64>, offset: Option<i64>) -> Self {
105 Self { limit, offset }
106 }
107
108 pub fn with_limit(limit: i64) -> Self {
110 Self {
111 limit: Some(limit),
112 offset: None,
113 }
114 }
115
116 pub fn with_offset(limit: i64, offset: i64) -> Self {
118 Self {
119 limit: Some(limit),
120 offset: Some(offset),
121 }
122 }
123
124 pub fn is_empty(&self) -> bool {
126 self.limit.is_none() && self.offset.is_none()
127 }
128
129 pub fn offset_or_default(&self) -> i64 {
131 self.offset.unwrap_or(0)
132 }
133}
134
135#[derive(Debug, Clone, Default, Serialize, Deserialize)]
137pub struct OrderAndPagination {
138 pub order_by: Vec<OrderByField>,
140 pub pagination: PaginationInput,
142}
143
144impl OrderAndPagination {
145 pub fn new(order_by: Vec<OrderByField>, pagination: PaginationInput) -> Self {
147 Self {
148 order_by,
149 pagination,
150 }
151 }
152
153 pub fn to_order_terms(&self) -> Vec<OrderTerm> {
155 self.order_by.iter().map(|f| f.to_order_term()).collect()
156 }
157}
158
159pub fn parse_order_enum(value: &str) -> Option<OrderByField> {
161 if let Some(pos) = value.rfind('_') {
163 let (field, direction) = value.split_at(pos);
164 let direction = &direction[1..]; let dir = match direction {
167 "ASC" => OrderDirection::Asc,
168 "DESC" => OrderDirection::Desc,
169 _ => return None,
170 };
171
172 Some(OrderByField {
173 field: field.to_string(),
174 direction: dir,
175 nulls: None,
176 })
177 } else {
178 None
179 }
180}
181
182pub fn make_order_enum(field: &str, direction: OrderDirection) -> String {
184 let dir_str = match direction {
185 OrderDirection::Asc => "ASC",
186 OrderDirection::Desc => "DESC",
187 };
188 format!("{}_{}", field, dir_str)
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use pretty_assertions::assert_eq;
195
196 #[test]
201 fn test_order_direction_default() {
202 let dir = OrderDirection::default();
203 assert_eq!(dir, OrderDirection::Asc);
204 }
205
206 #[test]
207 fn test_order_direction_to_core() {
208 let asc: CoreOrderDirection = OrderDirection::Asc.into();
209 assert!(matches!(asc, CoreOrderDirection::Asc));
210
211 let desc: CoreOrderDirection = OrderDirection::Desc.into();
212 assert!(matches!(desc, CoreOrderDirection::Desc));
213 }
214
215 #[test]
220 fn test_nulls_order_to_core() {
221 let first: OrderNulls = NullsOrder::First.into();
222 assert!(matches!(first, OrderNulls::First));
223
224 let last: OrderNulls = NullsOrder::Last.into();
225 assert!(matches!(last, OrderNulls::Last));
226 }
227
228 #[test]
233 fn test_order_by_field_asc() {
234 let field = OrderByField::asc("name");
235 assert_eq!(field.field, "name");
236 assert_eq!(field.direction, OrderDirection::Asc);
237 assert!(field.nulls.is_none());
238 }
239
240 #[test]
241 fn test_order_by_field_desc() {
242 let field = OrderByField::desc("created_at");
243 assert_eq!(field.field, "created_at");
244 assert_eq!(field.direction, OrderDirection::Desc);
245 }
246
247 #[test]
248 fn test_order_by_field_with_nulls() {
249 let field = OrderByField::desc("name").with_nulls(NullsOrder::Last);
250 assert_eq!(field.nulls, Some(NullsOrder::Last));
251 }
252
253 #[test]
254 fn test_order_by_field_to_order_term() {
255 let field = OrderByField::desc("name").with_nulls(NullsOrder::First);
256 let term = field.to_order_term();
257
258 match term {
259 OrderTerm::Field {
260 field,
261 direction,
262 nulls,
263 } => {
264 assert_eq!(field.name, "name");
265 assert!(matches!(direction, Some(CoreOrderDirection::Desc)));
266 assert!(matches!(nulls, Some(OrderNulls::First)));
267 }
268 _ => panic!("Expected Field order term"),
269 }
270 }
271
272 #[test]
277 fn test_pagination_default() {
278 let pagination = PaginationInput::default();
279 assert!(pagination.limit.is_none());
280 assert!(pagination.offset.is_none());
281 assert!(pagination.is_empty());
282 }
283
284 #[test]
285 fn test_pagination_with_limit() {
286 let pagination = PaginationInput::with_limit(10);
287 assert_eq!(pagination.limit, Some(10));
288 assert!(pagination.offset.is_none());
289 assert!(!pagination.is_empty());
290 }
291
292 #[test]
293 fn test_pagination_with_offset() {
294 let pagination = PaginationInput::with_offset(10, 20);
295 assert_eq!(pagination.limit, Some(10));
296 assert_eq!(pagination.offset, Some(20));
297 assert!(!pagination.is_empty());
298 }
299
300 #[test]
301 fn test_pagination_offset_or_default() {
302 let pagination = PaginationInput::default();
303 assert_eq!(pagination.offset_or_default(), 0);
304
305 let pagination = PaginationInput::with_offset(10, 5);
306 assert_eq!(pagination.offset_or_default(), 5);
307 }
308
309 #[test]
314 fn test_order_and_pagination_default() {
315 let oap = OrderAndPagination::default();
316 assert!(oap.order_by.is_empty());
317 assert!(oap.pagination.is_empty());
318 }
319
320 #[test]
321 fn test_order_and_pagination_new() {
322 let oap = OrderAndPagination::new(
323 vec![OrderByField::desc("created_at")],
324 PaginationInput::with_limit(10),
325 );
326
327 assert_eq!(oap.order_by.len(), 1);
328 assert_eq!(oap.pagination.limit, Some(10));
329 }
330
331 #[test]
332 fn test_order_and_pagination_to_order_terms() {
333 let oap = OrderAndPagination::new(
334 vec![OrderByField::desc("created_at"), OrderByField::asc("name")],
335 PaginationInput::default(),
336 );
337
338 let terms = oap.to_order_terms();
339 assert_eq!(terms.len(), 2);
340 }
341
342 #[test]
347 fn test_parse_order_enum_asc() {
348 let field = parse_order_enum("name_ASC").unwrap();
349 assert_eq!(field.field, "name");
350 assert_eq!(field.direction, OrderDirection::Asc);
351 }
352
353 #[test]
354 fn test_parse_order_enum_desc() {
355 let field = parse_order_enum("created_at_DESC").unwrap();
356 assert_eq!(field.field, "created_at");
357 assert_eq!(field.direction, OrderDirection::Desc);
358 }
359
360 #[test]
361 fn test_parse_order_enum_underscore_field() {
362 let field = parse_order_enum("created_at_ASC").unwrap();
363 assert_eq!(field.field, "created_at");
364 assert_eq!(field.direction, OrderDirection::Asc);
365 }
366
367 #[test]
368 fn test_parse_order_enum_invalid() {
369 assert!(parse_order_enum("name").is_none());
370 assert!(parse_order_enum("name_INVALID").is_none());
371 }
372
373 #[test]
374 fn test_make_order_enum() {
375 assert_eq!(make_order_enum("id", OrderDirection::Asc), "id_ASC");
376 assert_eq!(make_order_enum("name", OrderDirection::Desc), "name_DESC");
377 assert_eq!(
378 make_order_enum("created_at", OrderDirection::Asc),
379 "created_at_ASC"
380 );
381 }
382
383 #[test]
384 fn test_order_enum_roundtrip() {
385 let original = OrderByField::desc("user_id");
386 let enum_value = make_order_enum(&original.field, original.direction);
387 let parsed = parse_order_enum(&enum_value).unwrap();
388
389 assert_eq!(parsed.field, original.field);
390 assert_eq!(parsed.direction, original.direction);
391 }
392}