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