1use crate::filter::Filter;
6use crate::sort::Sort;
7use crate::{
8 CursorDirection, CursorParams, FilterOperator, FilterParams, FilterValue,
9 IntoParams, SearchParams, SortingParams, params::Params,
10};
11use std::marker::PhantomData;
12
13#[derive(Debug)]
19pub struct ParamsBuilder {
20 params: Params,
21}
22
23impl Default for ParamsBuilder {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl ParamsBuilder {
30 pub fn new() -> Self {
31 Self {
32 params: Params::default(),
33 }
34 }
35
36 pub fn sort(self) -> SortBuilder<Self, Safe> {
40 SortBuilder::with_parent(self)
41 }
42
43 pub fn filter(self) -> FilterBuilder<Self> {
47 FilterBuilder::with_parent(self)
48 }
49
50 pub fn search(self) -> SearchBuilder<Self> {
54 SearchBuilder::with_parent(self)
55 }
56
57 pub fn serial(self) -> SerialBuilder<Self> {
61 SerialBuilder::with_parent(self)
62 }
63
64 pub fn slice(self) -> SliceBuilder<Self> {
68 SliceBuilder::with_parent(self)
69 }
70
71 pub fn cursor(self) -> CursorBuilder<Self, Initial> {
75 CursorBuilder::with_parent(self)
76 }
77
78 pub fn limit(mut self, limit: u32) -> Self {
82 self.params.limit = Some(crate::pagination::LimitParam(limit));
83 self
84 }
85
86 pub fn offset(mut self, offset: u32) -> Self {
87 self.params.offset = Some(crate::pagination::OffsetParam(offset));
88 self
89 }
90
91 pub fn build(self) -> Params {
95 self.params
96 }
97}
98
99pub struct Safe;
106pub struct Unsafe;
107
108pub struct SortBuilder<P = (), S = Safe> {
109 parent: Option<P>,
110 sorts: SortingParams,
111 allowed_columns: Option<&'static [&'static str]>,
112 _security: PhantomData<S>,
113}
114
115impl Default for SortBuilder<(), Safe> {
116 fn default() -> Self {
117 Self::new()
118 }
119}
120
121impl<S> std::fmt::Debug for SortBuilder<(), S> {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 f.debug_struct("SortBuilder")
124 .field("sorts", &self.sorts)
125 .field("allowed_columns", &self.allowed_columns)
126 .finish()
127 }
128}
129
130impl SortBuilder<(), Safe> {
131 pub fn new() -> Self {
133 Self {
134 parent: None,
135 sorts: SortingParams::new(),
136 allowed_columns: None,
137 _security: PhantomData,
138 }
139 }
140
141 pub fn build(self) -> Option<SortingParams> {
143 if self.sorts.is_empty() {
144 None
145 } else {
146 Some(self.sorts)
147 }
148 }
149}
150
151impl<P> SortBuilder<P, Safe> {
153 fn with_parent(parent: P) -> Self {
154 Self {
155 parent: Some(parent),
156 sorts: SortingParams::new(),
157 allowed_columns: None,
158 _security: PhantomData,
159 }
160 }
161
162 pub fn asc(mut self, field: &'static str) -> Self {
164 self.sorts = self.sorts.asc(field);
165 self
166 }
167
168 pub fn desc(mut self, field: &'static str) -> Self {
170 self.sorts = self.sorts.desc(field);
171 self
172 }
173
174 pub fn with_allowed_columns(self, allowed_columns: &'static [&'static str]) -> SortBuilder<P, Unsafe> {
176 SortBuilder {
177 parent: self.parent,
178 sorts: self.sorts.with_allowed_columns(allowed_columns),
179 allowed_columns: Some(allowed_columns),
180 _security: PhantomData,
181 }
182 }
183}
184
185impl SortBuilder<(), Unsafe> {
187 pub fn build(self) -> Option<SortingParams> {
189 if self.sorts.is_empty() {
190 None
191 } else {
192 Some(self.sorts)
193 }
194 }
195}
196
197impl<P> SortBuilder<P, Unsafe> {
198 pub fn asc_unsafe(mut self, field: impl Into<String>) -> Self {
200 let sort = Sort::asc_unsafe(field.into());
201 self.sorts = self.sorts.push(sort);
202 self
203 }
204
205 pub fn desc_unsafe(mut self, field: impl Into<String>) -> Self {
207 let sort = Sort::desc_unsafe(field.into());
208 self.sorts = self.sorts.push(sort);
209 self
210 }
211
212}
213
214impl<P, S> SortBuilder<P, S> {
216 pub fn nulls_first(mut self) -> Self {
218 self.sorts = self.sorts.apply_nulls_first();
219 self
220 }
221
222 pub fn nulls_last(mut self) -> Self {
224 self.sorts = self.sorts.apply_nulls_last();
225 self
226 }
227
228 pub fn nulls_default(mut self) -> Self {
230 self.sorts = self.sorts.apply_nulls_default();
231 self
232 }
233
234 pub fn done(self) -> P
236 where
237 P: HasParams,
238 {
239 #[allow(clippy::expect_used)]
240 let mut parent = self
241 .parent
242 .expect("SortBuilder::done called without a parent (programming error)");
243
244 if !self.sorts.is_empty() {
245 if let Some(existing_sorts) = parent.params_mut().sort_by.take() {
246 parent.params_mut().sort_by = Some(existing_sorts.extend_with(self.sorts));
247 } else {
248 parent.params_mut().sort_by = Some(self.sorts);
249 }
250 }
251 parent
252 }
253}
254
255pub struct FilterBuilder<P = ()> {
262 parent: Option<P>,
263 filters: FilterParams,
264}
265
266impl Default for FilterBuilder<()> {
267 fn default() -> Self {
268 Self::new()
269 }
270}
271
272impl std::fmt::Debug for FilterBuilder<()> {
273 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
274 f.debug_struct("FilterBuilder")
275 .field("filters", &self.filters)
276 .finish()
277 }
278}
279
280impl FilterBuilder<()> {
281 pub fn new() -> Self {
283 Self {
284 parent: None,
285 filters: FilterParams::default(),
286 }
287 }
288
289 pub fn build(self) -> FilterParams {
291 self.filters
292 }
293}
294
295impl<P> FilterBuilder<P> {
296 pub fn with_parent(parent: P) -> Self {
298 Self {
299 parent: Some(parent),
300 filters: FilterParams::default(),
301 }
302 }
303
304 fn push(
307 mut self,
308 field: impl Into<String>,
309 op: FilterOperator,
310 value: impl Into<FilterValue>,
311 ) -> Self {
312 self.filters.filters.push(Filter::new(field, op, value));
313 self
314 }
315
316 pub fn eq(self, field: impl Into<String>, value: impl Into<FilterValue>) -> Self {
317 self.push(field, FilterOperator::Eq, value)
318 }
319
320 pub fn ne(self, field: impl Into<String>, value: impl Into<FilterValue>) -> Self {
321 self.push(field, FilterOperator::Ne, value)
322 }
323
324 pub fn gt(self, field: impl Into<String>, value: impl Into<FilterValue>) -> Self {
325 self.push(field, FilterOperator::Gt, value)
326 }
327
328 pub fn lt(self, field: impl Into<String>, value: impl Into<FilterValue>) -> Self {
329 self.push(field, FilterOperator::Lt, value)
330 }
331
332 pub fn gte(self, field: impl Into<String>, value: impl Into<FilterValue>) -> Self {
333 self.push(field, FilterOperator::Gte, value)
334 }
335
336 pub fn lte(self, field: impl Into<String>, value: impl Into<FilterValue>) -> Self {
337 self.push(field, FilterOperator::Lte, value)
338 }
339
340 pub fn like(self, field: impl Into<String>, pat: impl Into<String>) -> Self {
343 self.push(field, FilterOperator::Like, pat.into())
344 }
345
346 pub fn ilike(self, field: impl Into<String>, pat: impl Into<String>) -> Self {
349 self.push(field, FilterOperator::ILike, pat.into())
350 }
351
352 pub fn like_pattern(self, field: impl Into<String>, pat: impl Into<String>) -> Self {
356 self.push(field, FilterOperator::UnsafeLike, pat.into())
357 }
358
359 pub fn r#in(
360 self,
361 field: impl Into<String>,
362 values: impl IntoIterator<Item = impl Into<FilterValue>>,
363 ) -> Self {
364 let array = FilterValue::Array(values.into_iter().map(Into::into).collect());
365 self.push(field, FilterOperator::In, array)
366 }
367
368 pub fn in_values(
370 self,
371 field: impl Into<String>,
372 values: impl IntoIterator<Item = impl Into<FilterValue>>,
373 ) -> Self {
374 self.r#in(field, values)
375 }
376
377 pub fn not_in(
378 self,
379 field: impl Into<String>,
380 values: impl IntoIterator<Item = impl Into<FilterValue>>,
381 ) -> Self {
382 self.push(
383 field,
384 FilterOperator::NotIn,
385 FilterValue::Array(values.into_iter().map(Into::into).collect()),
386 )
387 }
388
389 pub fn between(
390 self,
391 field: impl Into<String>,
392 min: impl Into<FilterValue>,
393 max: impl Into<FilterValue>,
394 ) -> Self {
395 self.push(
396 field,
397 FilterOperator::Between,
398 FilterValue::Array(vec![min.into(), max.into()]),
399 )
400 }
401
402 pub fn is_null(self, field: impl Into<String>) -> Self {
403 self.push(field, FilterOperator::IsNull, FilterValue::Null)
404 }
405
406 pub fn is_not_null(self, field: impl Into<String>) -> Self {
407 self.push(field, FilterOperator::IsNotNull, FilterValue::Null)
408 }
409
410 pub fn contains(self, field: impl Into<String>, value: impl Into<FilterValue>) -> Self {
411 self.push(field, FilterOperator::Contains, value)
412 }
413
414 #[allow(clippy::should_implement_trait)]
418 pub fn not(mut self) -> Self {
419 if let Some(last_filter) = self.filters.filters.last_mut() {
420 last_filter.not = true;
421 }
422 self
423 }
424
425 pub fn done(self) -> P
427 where
428 P: HasParams,
429 {
430 #[allow(clippy::expect_used)]
431 let mut parent = self
432 .parent
433 .expect("FilterBuilder::done called without a parent (programming error)");
434 if let Some(ref mut existing_filters) = parent.params_mut().filters {
435 existing_filters.filters.extend(self.filters.filters);
436 } else {
437 parent.params_mut().filters = Some(self.filters);
438 }
439 parent
440 }
441}
442
443pub struct SearchBuilder<P = ()> {
450 parent: Option<P>,
451 search: SearchParams,
452}
453
454impl Default for SearchBuilder<()> {
455 fn default() -> Self {
456 Self::new()
457 }
458}
459
460impl std::fmt::Debug for SearchBuilder<()> {
461 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
462 f.debug_struct("SearchBuilder")
463 .field("search", &self.search)
464 .finish()
465 }
466}
467
468impl SearchBuilder<()> {
469 pub fn new() -> Self {
470 Self {
471 parent: None,
472 search: SearchParams::new("", vec![]),
473 }
474 }
475
476 pub fn build(self) -> SearchParams {
477 self.search
478 }
479}
480
481impl<P> SearchBuilder<P> {
482 pub fn with_parent(parent: P) -> Self {
483 Self {
484 parent: Some(parent),
485 search: SearchParams::new("", vec![]),
486 }
487 }
488
489 pub fn query(mut self, q: impl Into<String>) -> Self {
490 self.search.query = q.into();
491 self
492 }
493
494 pub fn search<I, S>(mut self, query: impl Into<String>, fields: I) -> Self
496 where
497 I: IntoIterator<Item = S>,
498 S: Into<String>,
499 {
500 self.search.query = query.into();
501 self.search.fields = fields.into_iter().map(|s| s.into()).collect();
502 self
503 }
504
505 pub fn fields<I, S>(mut self, fields: I) -> Self
506 where
507 I: IntoIterator<Item = S>,
508 S: Into<String>,
509 {
510 self.search.fields = fields.into_iter().map(Into::into).collect();
511 self
512 }
513
514 pub fn exact(mut self, yes: bool) -> Self {
515 self.search = self.search.with_exact_match(yes);
516 self
517 }
518
519 pub fn case_sensitive(mut self, yes: bool) -> Self {
520 self.search = self.search.with_case_sensitive(yes);
521 self
522 }
523
524 pub fn done(self) -> P
525 where
526 P: HasParams,
527 {
528 #[allow(clippy::expect_used)]
529 let mut parent = self
530 .parent
531 .expect("SearchBuilder::done called without a parent (programming error)");
532
533 if !self.search.query.is_empty() {
534 parent.params_mut().search = Some(self.search);
535 }
536
537 parent
538 }
539}
540
541pub struct SerialBuilder<P = ()> {
548 parent: Option<P>,
549 serial: crate::serial::SerialParams,
550}
551
552impl Default for SerialBuilder<()> {
553 fn default() -> Self {
554 Self::new()
555 }
556}
557
558impl std::fmt::Debug for SerialBuilder<()> {
559 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
560 f.debug_struct("SerialBuilder")
561 .field("serial", &self.serial)
562 .finish()
563 }
564}
565
566impl SerialBuilder<()> {
567 pub fn new() -> Self {
568 Self {
569 parent: None,
570 serial: crate::serial::SerialParams::default(),
571 }
572 }
573
574 pub fn with_page(page: u32, per_page: u32) -> Self {
575 Self {
576 parent: None,
577 serial: crate::serial::SerialParams::new(page, per_page),
578 }
579 }
580
581 pub fn build(self) -> crate::serial::SerialParams {
582 self.serial
583 }
584}
585
586impl<P> SerialBuilder<P> {
587 pub fn with_parent(parent: P) -> Self {
588 Self {
589 parent: Some(parent),
590 serial: crate::serial::SerialParams::default(),
591 }
592 }
593
594 pub fn page(mut self, page: u32, per_page: u32) -> Self {
595 self.serial = crate::serial::SerialParams::new(page, per_page);
596 self
597 }
598
599 pub fn done(self) -> P
600 where
601 P: HasParams,
602 {
603 #[allow(clippy::expect_used)]
604 let mut parent = self
605 .parent
606 .expect("SerialBuilder::done called without a parent (programming error)");
607 let params = parent.params_mut();
608 let limit = self.serial.limit();
609 let offset = self.serial.offset();
610 params.pagination = Some(crate::pagination::Pagination::Serial(self.serial));
611 params.limit = Some(crate::pagination::LimitParam(limit));
612 params.offset = Some(crate::pagination::OffsetParam(offset));
613 parent
614 }
615}
616
617pub struct SliceBuilder<P = ()> {
624 parent: Option<P>,
625 slice: crate::slice::SliceParams,
626}
627
628impl Default for SliceBuilder<()> {
629 fn default() -> Self {
630 Self::new()
631 }
632}
633
634impl std::fmt::Debug for SliceBuilder<()> {
635 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
636 f.debug_struct("SliceBuilder")
637 .field("slice", &self.slice)
638 .finish()
639 }
640}
641
642impl SliceBuilder<()> {
643 pub fn new() -> Self {
644 Self {
645 parent: None,
646 slice: crate::slice::SliceParams::default(),
647 }
648 }
649
650 pub fn with_page(page: u32, per_page: u32) -> Self {
651 Self {
652 parent: None,
653 slice: crate::slice::SliceParams::new(page, per_page),
654 }
655 }
656
657 pub fn build(self) -> crate::slice::SliceParams {
658 self.slice
659 }
660}
661
662impl<P> SliceBuilder<P> {
663 pub fn with_parent(parent: P) -> Self {
664 Self {
665 parent: Some(parent),
666 slice: crate::slice::SliceParams::default(),
667 }
668 }
669
670 pub fn page(mut self, page: u32, per_page: u32) -> Self {
671 self.slice = crate::slice::SliceParams::new(page, per_page);
672 self
673 }
674
675 pub fn enable_total_count(mut self) -> Self {
676 self.slice = self.slice.with_disable_total_count(false);
677 self
678 }
679
680 pub fn done(self) -> P
681 where
682 P: HasParams,
683 {
684 #[allow(clippy::expect_used)]
685 let mut parent = self
686 .parent
687 .expect("SliceBuilder::done called without a parent (programming error)");
688 let params = parent.params_mut();
689 let limit = self.slice.limit();
690 let offset = self.slice.offset();
691 params.pagination = Some(crate::pagination::Pagination::Slice(self.slice));
692 params.limit = Some(crate::pagination::LimitParam(limit));
693 params.offset = Some(crate::pagination::OffsetParam(offset));
694 parent
695 }
696}
697
698pub struct Initial;
708
709pub struct FirstPage;
712
713pub struct After;
715
716pub struct Before;
718
719
720pub struct CursorBuilder<P = (), S = Initial> {
721 parent: Option<P>,
722 cursor: CursorParams,
723 _state: PhantomData<S>,
724}
725
726impl Default for CursorBuilder<(), Initial> {
727 fn default() -> Self {
728 Self::new()
729 }
730}
731
732impl<S> std::fmt::Debug for CursorBuilder<(), S> {
733 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
734 f.debug_struct("CursorBuilder")
735 .field("cursor", &self.cursor)
736 .finish()
737 }
738}
739
740impl CursorBuilder<(), Initial> {
741 pub fn new() -> Self {
742 Self {
743 parent: None,
744 cursor: CursorParams::default(),
745 _state: PhantomData,
746 }
747 }
748
749 pub fn build(self) -> CursorParams {
750 self.cursor
751 }
752}
753
754impl<P> CursorBuilder<P, Initial> {
757 pub fn with_parent(parent: P) -> Self {
758 Self {
759 parent: Some(parent),
760 cursor: CursorParams::default(),
761 _state: PhantomData,
762 }
763 }
764
765 pub fn first_page(self) -> CursorBuilder<P, FirstPage> {
767 CursorBuilder {
768 parent: self.parent,
769 cursor: CursorParams::default(), _state: PhantomData,
771 }
772 }
773
774 pub fn after(
776 self,
777 value: impl Into<FilterValue>
778 ) -> CursorBuilder<P, After> {
779 let mut cursor = CursorParams::default();
780 cursor = cursor.and_field(value.into());
781 cursor.direction = Some(CursorDirection::After);
782
783 CursorBuilder {
784 parent: self.parent,
785 cursor,
786 _state: PhantomData,
787 }
788 }
789
790 pub fn before(
792 self,
793 value: impl Into<FilterValue>
794 ) -> CursorBuilder<P, Before> {
795 let mut cursor = CursorParams::default();
796 cursor = cursor.and_field(value.into());
797 cursor.direction = Some(CursorDirection::Before);
798
799 CursorBuilder {
800 parent: self.parent,
801 cursor,
802 _state: PhantomData,
803 }
804 }
805
806 pub fn next_cursor<T: crate::CursorSecureExtract>(self, token: impl Into<String>) -> CursorBuilder<P, After> {
808 self.decoded::<T, After>(token, CursorDirection::After)
809 }
810
811 pub fn prev_cursor<T: crate::CursorSecureExtract>(self, token: impl Into<String>) -> CursorBuilder<P, Before> {
813 self.decoded::<T, Before>(token, CursorDirection::Before)
814 }
815
816 fn decoded<T: crate::CursorSecureExtract, S>(
818 self,
819 token: impl Into<String>,
820 direction: CursorDirection,
821 ) -> CursorBuilder<P, S> {
822 let token = token.into();
823
824 let cursor = match T::decode(&token) {
825 Ok(decoded_values) => {
826 CursorParams::from_values(decoded_values, direction)
827 }
828 Err(e) => {
829 CursorParams::with_error(direction, e.to_string())
830 }
831 };
832
833 CursorBuilder {
834 parent: self.parent,
835 cursor,
836 _state: PhantomData,
837 }
838 }
839}
840
841impl<P> CursorBuilder<P, FirstPage> {
844
845 pub fn done(self) -> P
846 where
847 P: HasParams,
848 {
849 #[allow(clippy::expect_used)]
850 let mut parent = self
851 .parent
852 .expect("CursorBuilder::done called without a parent (programming error)");
853 let params = parent.params_mut();
854 params.pagination = Some(crate::pagination::Pagination::Cursor(self.cursor));
856 parent
857 }
858}
859
860impl<P> CursorBuilder<P, After> {
863
864 pub fn and_field(mut self, value: impl Into<FilterValue>) -> Self {
866 self.cursor = self.cursor.and_field(value.into());
867 self
868 }
869
870 pub fn done(self) -> P
871 where
872 P: HasParams,
873 {
874 #[allow(clippy::expect_used)]
875 let mut parent = self
876 .parent
877 .expect("CursorBuilder::done called without a parent (programming error)");
878 let params = parent.params_mut();
879 params.pagination = Some(crate::pagination::Pagination::Cursor(self.cursor));
880 parent
881 }
882}
883
884impl<P> CursorBuilder<P, Before> {
887
888 pub fn and_field(mut self, value: impl Into<FilterValue>) -> Self {
890 self.cursor = self.cursor.and_field(value.into());
891 self
892 }
893
894 pub fn done(self) -> P
895 where
896 P: HasParams,
897 {
898 #[allow(clippy::expect_used)]
899 let mut parent = self
900 .parent
901 .expect("CursorBuilder::done called without a parent (programming error)");
902 let params = parent.params_mut();
903 params.pagination = Some(crate::pagination::Pagination::Cursor(self.cursor));
904 parent
905 }
906}
907
908pub trait HasParams {
912 fn params_mut(&mut self) -> &mut Params;
913}
914
915impl HasParams for ParamsBuilder {
916 fn params_mut(&mut self) -> &mut Params {
917 &mut self.params
918 }
919}
920
921impl IntoParams for ParamsBuilder {
922 fn into_params(self) -> Params {
923 self.params
924 }
925}
926
927impl IntoParams for FilterBuilder<()> {
928 fn into_params(self) -> Params {
929 Params {
930 filters: if self.filters.filters.is_empty() {
931 None
932 } else {
933 Some(self.filters)
934 },
935 ..Default::default()
936 }
937 }
938}
939
940impl IntoParams for SearchBuilder<()> {
941 fn into_params(self) -> Params {
942 Params {
943 search: if self.search.query.is_empty() {
944 None
945 } else {
946 Some(self.search)
947 },
948 ..Default::default()
949 }
950 }
951}
952
953impl IntoParams for SerialBuilder<()> {
954 fn into_params(self) -> Params {
955 let limit = self.serial.limit();
956 let offset = self.serial.offset();
957 Params {
958 pagination: Some(crate::pagination::Pagination::Serial(self.serial)),
959 limit: Some(crate::pagination::LimitParam(limit)),
960 offset: Some(crate::pagination::OffsetParam(offset)),
961 ..Default::default()
962 }
963 }
964}
965
966impl IntoParams for SliceBuilder<()> {
967 fn into_params(self) -> Params {
968 let limit = self.slice.limit();
969 let offset = self.slice.offset();
970 Params {
971 pagination: Some(crate::pagination::Pagination::Slice(self.slice)),
972 limit: Some(crate::pagination::LimitParam(limit)),
973 offset: Some(crate::pagination::OffsetParam(offset)),
974 ..Default::default()
975 }
976 }
977}
978
979impl IntoParams for CursorBuilder<(), Initial> {
981 fn into_params(self) -> Params {
982 Params {
983 ..Default::default()
985 }
986 }
987}
988
989impl IntoParams for CursorBuilder<(), FirstPage> {
990 fn into_params(self) -> Params {
991 Params {
992 pagination: Some(crate::pagination::Pagination::Cursor(self.cursor)),
993 ..Default::default()
994 }
995 }
996}
997
998impl IntoParams for CursorBuilder<(), After> {
999 fn into_params(self) -> Params {
1000 Params {
1001 pagination: Some(crate::pagination::Pagination::Cursor(self.cursor)),
1002 ..Default::default()
1003 }
1004 }
1005}
1006
1007impl IntoParams for CursorBuilder<(), Before> {
1008 fn into_params(self) -> Params {
1009 Params {
1010 pagination: Some(crate::pagination::Pagination::Cursor(self.cursor)),
1011 ..Default::default()
1012 }
1013 }
1014}
1015
1016
1017impl<S> IntoParams for SortBuilder<(), S> {
1018 fn into_params(self) -> Params {
1019 Params {
1020 sort_by: if self.sorts.is_empty() {
1021 None
1022 } else {
1023 Some(self.sorts)
1024 },
1025 ..Default::default()
1026 }
1027 }
1028}
1029#[cfg(test)]
1030mod security_tests {
1031 use super::*;
1032
1033 #[test]
1034 fn test_safe_sort_builder_compile_time_safety() {
1035 let params = ParamsBuilder::new()
1037 .sort()
1038 .asc("id") .desc("created_at") .done()
1041 .build();
1042
1043 assert!(params.sort_by.is_some());
1044 let sort_by = params.sort_by.unwrap();
1045 assert_eq!(sort_by.sorts().len(), 2);
1046 assert_eq!(sort_by.sorts()[0].field, "id");
1047 assert!(sort_by.sorts()[0].is_asc());
1048 assert_eq!(sort_by.sorts()[1].field, "created_at");
1049 assert!(!sort_by.sorts()[1].is_asc());
1050 }
1051
1052 #[test]
1053 fn test_unsafe_sort_builder_with_runtime_validation() {
1054 let dynamic_field = "name".to_string();
1056 let invalid_field = "malicious_field".to_string();
1057
1058 let params = ParamsBuilder::new()
1059 .sort()
1060 .with_allowed_columns(&["id", "name", "created_at", "age"])
1061 .asc_unsafe(dynamic_field) .desc_unsafe(invalid_field) .done()
1064 .build();
1065
1066 assert!(params.sort_by.is_some());
1067 let sort_by = params.sort_by.unwrap();
1068 assert_eq!(sort_by.sorts().len(), 2);
1069
1070 assert_eq!(sort_by.sorts()[0].field, "name");
1072 assert!(sort_by.sorts()[0].is_asc());
1073 assert_eq!(sort_by.sorts()[1].field, "malicious_field");
1074 assert!(!sort_by.sorts()[1].is_asc());
1075
1076 assert!(sort_by.has_unsafe_fields());
1078
1079 assert!(sort_by.validate_fields().is_err());
1081 }
1082
1083 #[test]
1084 fn test_standalone_sort_builder() {
1085 let safe_sort = SortBuilder::new()
1087 .asc("id")
1088 .desc("created_at")
1089 .build();
1090
1091 assert!(safe_sort.is_some());
1092 let sort_params = safe_sort.unwrap();
1093 assert_eq!(sort_params.sorts().len(), 2);
1094
1095 let unsafe_sort = SortBuilder::new()
1097 .with_allowed_columns(&["id", "name"])
1098 .asc_unsafe("invalid_field".to_string()) .build();
1100
1101 assert!(unsafe_sort.is_some());
1102 let sort_params = unsafe_sort.unwrap();
1103 assert_eq!(sort_params.sorts().len(), 1);
1104 assert_eq!(sort_params.sorts()[0].field, "invalid_field"); assert!(sort_params.has_unsafe_fields()); assert!(sort_params.validate_fields().is_err());
1109 }
1110
1111 #[test]
1112 fn test_runtime_validation_success() {
1113 let params = ParamsBuilder::new()
1115 .sort()
1116 .with_allowed_columns(&["id", "name", "email"])
1117 .asc_unsafe("name".to_string()) .desc_unsafe("id".to_string()) .done()
1120 .build();
1121
1122 let sort_by = params.sort_by.unwrap();
1123 assert!(sort_by.has_unsafe_fields());
1124
1125 assert!(sort_by.validate_fields().is_ok());
1127 }
1128}