paginator_rs/
builder.rs

1use paginator_utils::{
2    Cursor, CursorDirection, CursorValue, Filter, FilterOperator, FilterValue,
3    IntoPaginationParams, PaginationParams, SearchParams, SortDirection,
4};
5use std::marker::PhantomData;
6
7//
8// ========================
9//   PAGINATOR ROOT BUILDER
10// ========================
11//
12pub struct Paginator<State = Ready> {
13    params: PaginationParams,
14    _state: PhantomData<State>,
15}
16
17/// Marker state
18pub struct Ready;
19
20impl Default for Paginator {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26impl Paginator {
27    pub fn new() -> Self {
28        Self {
29            params: PaginationParams::default(),
30            _state: PhantomData,
31        }
32    }
33
34    //
35    // -------------- BASIC CONFIG --------------
36    //
37    pub fn page(mut self, page: u32) -> Self {
38        self.params.page = page.max(1);
39        self
40    }
41
42    pub fn per_page(mut self, per_page: u32) -> Self {
43        self.params.per_page = per_page.clamp(1, 100);
44        self
45    }
46
47    //
48    // -------------- SORT BUILDER --------------
49    //
50    pub fn sort(self) -> SortBuilder<Self> {
51        SortBuilder::new(self)
52    }
53
54    //
55    // -------------- FILTER BUILDER --------------
56    //
57    pub fn filter(self) -> FilterBuilder<Self> {
58        FilterBuilder::with_parent(self)
59    }
60
61    //
62    // -------------- SEARCH BUILDER --------------
63    //
64    pub fn search(self) -> SearchBuilder<Self> {
65        SearchBuilder::with_parent(self)
66    }
67
68    //
69    // -------------- CURSOR BUILDER --------------
70    //
71    pub fn cursor(self) -> CursorBuilder<Self> {
72        CursorBuilder::with_parent(self)
73    }
74
75    pub fn disable_total_count(mut self) -> Self {
76        self.params.disable_total_count = true;
77        self
78    }
79
80    //
81    // -------------- FINAL BUILD --------------
82    //
83    pub fn build(self) -> PaginationParams {
84        self.params
85    }
86}
87
88//
89// ========================
90//       SORT BUILDER
91// ========================
92//
93
94pub struct SortBuilder<P> {
95    parent: P,
96}
97
98impl<P> SortBuilder<P> {
99    fn new(parent: P) -> Self {
100        Self { parent }
101    }
102
103    pub fn asc(mut self, field: impl Into<String>) -> P
104    where
105        P: HasParams,
106    {
107        let mut p = self.parent;
108        p.params_mut().sort_by = Some(field.into());
109        p.params_mut().sort_direction = Some(SortDirection::Asc);
110        p
111    }
112
113    pub fn desc(mut self, field: impl Into<String>) -> P
114    where
115        P: HasParams,
116    {
117        let mut p = self.parent;
118        p.params_mut().sort_by = Some(field.into());
119        p.params_mut().sort_direction = Some(SortDirection::Desc);
120        p
121    }
122}
123
124//
125// ========================
126//      FILTER BUILDER
127// ========================
128//
129
130pub struct FilterBuilder<P = ()> {
131    parent: Option<P>,
132    filters: Vec<Filter>,
133}
134
135impl FilterBuilder<()> {
136    /// Create as standalone (no parent)
137    pub fn new() -> Self {
138        Self {
139            parent: None,
140            filters: Vec::new(),
141        }
142    }
143
144    /// Finish and return only the filters
145    pub fn build(self) -> Vec<Filter> {
146        self.filters
147    }
148}
149
150impl<P> FilterBuilder<P> {
151    /// Create with parent (Paginator, or any other)
152    pub fn with_parent(parent: P) -> Self {
153        Self {
154            parent: Some(parent),
155            filters: Vec::new(),
156        }
157    }
158
159    // --- PRIMITIVES ---
160
161    fn push(mut self, field: impl Into<String>, op: FilterOperator, value: FilterValue) -> Self {
162        self.filters.push(Filter::new(field, op, value));
163        self
164    }
165
166    pub fn eq(self, field: impl Into<String>, value: FilterValue) -> Self {
167        self.push(field, FilterOperator::Eq, value)
168    }
169
170    pub fn ne(self, field: impl Into<String>, value: FilterValue) -> Self {
171        self.push(field, FilterOperator::Ne, value)
172    }
173
174    pub fn gt(self, field: impl Into<String>, value: FilterValue) -> Self {
175        self.push(field, FilterOperator::Gt, value)
176    }
177
178    pub fn lt(self, field: impl Into<String>, value: FilterValue) -> Self {
179        self.push(field, FilterOperator::Lt, value)
180    }
181
182    pub fn gte(self, field: impl Into<String>, value: FilterValue) -> Self {
183        self.push(field, FilterOperator::Gte, value)
184    }
185
186    pub fn lte(self, field: impl Into<String>, value: FilterValue) -> Self {
187        self.push(field, FilterOperator::Lte, value)
188    }
189
190    pub fn like(self, field: impl Into<String>, pat: impl Into<String>) -> Self {
191        self.push(field, FilterOperator::Like, FilterValue::String(pat.into()))
192    }
193
194    pub fn ilike(self, field: impl Into<String>, pat: impl Into<String>) -> Self {
195        self.push(
196            field,
197            FilterOperator::ILike,
198            FilterValue::String(pat.into()),
199        )
200    }
201
202    pub fn r#in(self, field: impl Into<String>, values: Vec<FilterValue>) -> Self {
203        self.push(field, FilterOperator::In, FilterValue::Array(values))
204    }
205
206    pub fn not_in(self, field: impl Into<String>, values: Vec<FilterValue>) -> Self {
207        self.push(field, FilterOperator::NotIn, FilterValue::Array(values))
208    }
209
210    pub fn between(self, field: impl Into<String>, min: FilterValue, max: FilterValue) -> Self {
211        self.push(
212            field,
213            FilterOperator::Between,
214            FilterValue::Array(vec![min, max]),
215        )
216    }
217
218    pub fn is_null(self, field: impl Into<String>) -> Self {
219        self.push(field, FilterOperator::IsNull, FilterValue::Null)
220    }
221
222    pub fn is_not_null(self, field: impl Into<String>) -> Self {
223        self.push(field, FilterOperator::IsNotNull, FilterValue::Null)
224    }
225
226    pub fn contains(self, field: impl Into<String>, value: FilterValue) -> Self {
227        self.push(field, FilterOperator::Contains, value)
228    }
229
230    /// Finish and return to parent.
231    ///
232    /// Note: This method is only callable when the builder was created via a fluent chain
233    /// (e.g., `Paginator::new().filter()`), not when created standalone via `FilterBuilder::new()`.
234    /// The trait bound `P: HasParams` ensures this at compile time.
235    pub fn apply(self) -> P
236    where
237        P: HasParams,
238    {
239        // SAFETY: When P: HasParams, the builder must have been created via `with_parent`,
240        // which always sets parent to Some. Standalone builders (FilterBuilder<()>) cannot
241        // call this method because () does not implement HasParams.
242        let mut parent = self.parent.unwrap_or_else(|| {
243            panic!("BUG: FilterBuilder::apply called without a parent. This should be prevented by type system.")
244        });
245        parent.params_mut().filters.extend(self.filters);
246        parent
247    }
248}
249
250//
251// ========================
252//      SEARCH BUILDER
253// ========================
254//
255
256pub struct SearchBuilder<P = ()> {
257    parent: Option<P>,
258    query: Option<String>,
259    fields: Vec<String>,
260    exact: bool,
261    case_sensitive: bool,
262}
263
264impl SearchBuilder<()> {
265    pub fn new() -> Self {
266        Self {
267            parent: None,
268            query: None,
269            fields: Vec::new(),
270            exact: false,
271            case_sensitive: false,
272        }
273    }
274
275    pub fn build(self) -> Option<SearchParams> {
276        self.query.map(|q| {
277            let mut params = SearchParams::new(q, self.fields);
278            if self.exact {
279                params = params.with_exact_match(true);
280            }
281            if self.case_sensitive {
282                params = params.with_case_sensitive(true);
283            }
284            params
285        })
286    }
287}
288
289impl<P> SearchBuilder<P> {
290    pub fn with_parent(parent: P) -> Self {
291        Self {
292            parent: Some(parent),
293            query: None,
294            fields: Vec::new(),
295            exact: false,
296            case_sensitive: false,
297        }
298    }
299
300    pub fn query(mut self, q: impl Into<String>) -> Self {
301        self.query = Some(q.into());
302        self
303    }
304
305    pub fn fields<I, S>(mut self, fields: I) -> Self
306    where
307        I: IntoIterator<Item = S>,
308        S: Into<String>,
309    {
310        self.fields = fields.into_iter().map(Into::into).collect();
311        self
312    }
313
314    pub fn exact(mut self, yes: bool) -> Self {
315        self.exact = yes;
316        self
317    }
318
319    pub fn case_sensitive(mut self, yes: bool) -> Self {
320        self.case_sensitive = yes;
321        self
322    }
323
324    /// Finish and return to parent.
325    ///
326    /// Note: This method is only callable when the builder was created via a fluent chain
327    /// (e.g., `Paginator::new().search()`), not when created standalone via `SearchBuilder::new()`.
328    /// The trait bound `P: HasParams` ensures this at compile time.
329    pub fn apply(self) -> P
330    where
331        P: HasParams,
332    {
333        // SAFETY: When P: HasParams, the builder must have been created via `with_parent`,
334        // which always sets parent to Some. Standalone builders (SearchBuilder<()>) cannot
335        // call this method because () does not implement HasParams.
336        let mut parent = self.parent.unwrap_or_else(|| {
337            panic!("BUG: SearchBuilder::apply called without a parent. This should be prevented by type system.")
338        });
339
340        if let Some(q) = self.query {
341            let mut s = SearchParams::new(q, self.fields);
342            if self.exact {
343                s = s.with_exact_match(true);
344            }
345            if self.case_sensitive {
346                s = s.with_case_sensitive(true);
347            }
348            parent.params_mut().search = Some(s);
349        }
350
351        parent
352    }
353}
354
355//
356// ========================
357//      CURSOR BUILDER
358// ========================
359//
360
361pub struct CursorBuilder<P = ()> {
362    parent: Option<P>,
363    cursor: Option<Cursor>,
364}
365
366impl CursorBuilder<()> {
367    pub fn new() -> Self {
368        Self {
369            parent: None,
370            cursor: None,
371        }
372    }
373
374    pub fn build(self) -> Option<Cursor> {
375        self.cursor
376    }
377}
378
379impl<P> CursorBuilder<P> {
380    pub fn with_parent(parent: P) -> Self {
381        Self {
382            parent: Some(parent),
383            cursor: None,
384        }
385    }
386
387    pub fn after(mut self, field: impl Into<String>, value: CursorValue) -> Self {
388        self.cursor = Some(Cursor::new(field.into(), value, CursorDirection::After));
389        self
390    }
391
392    pub fn before(mut self, field: impl Into<String>, value: CursorValue) -> Self {
393        self.cursor = Some(Cursor::new(field.into(), value, CursorDirection::Before));
394        self
395    }
396
397    pub fn from_encoded(mut self, encoded: &str) -> Result<Self, String> {
398        self.cursor = Some(Cursor::decode(encoded)?);
399        Ok(self)
400    }
401
402    /// Finish and return to parent.
403    ///
404    /// Note: This method is only callable when the builder was created via a fluent chain
405    /// (e.g., `Paginator::new().cursor()`), not when created standalone via `CursorBuilder::new()`.
406    /// The trait bound `P: HasParams` ensures this at compile time.
407    pub fn apply(self) -> P
408    where
409        P: HasParams,
410    {
411        // SAFETY: When P: HasParams, the builder must have been created via `with_parent`,
412        // which always sets parent to Some. Standalone builders (CursorBuilder<()>) cannot
413        // call this method because () does not implement HasParams.
414        let mut parent = self.parent.unwrap_or_else(|| {
415            panic!("BUG: CursorBuilder::apply called without a parent. This should be prevented by type system.")
416        });
417        if let Some(cursor) = self.cursor {
418            parent.params_mut().cursor = Some(cursor);
419        }
420        parent
421    }
422}
423
424/// Trait for types that have params
425pub trait HasParams {
426    fn params_mut(&mut self) -> &mut PaginationParams;
427}
428
429impl<S> HasParams for Paginator<S> {
430    fn params_mut(&mut self) -> &mut PaginationParams {
431        &mut self.params
432    }
433}
434
435impl<S> IntoPaginationParams for Paginator<S> {
436    fn into_pagination_params(self) -> PaginationParams {
437        self.params
438    }
439}
440
441impl IntoPaginationParams for FilterBuilder<()> {
442    fn into_pagination_params(self) -> PaginationParams {
443        PaginationParams {
444            filters: self.filters,
445            ..Default::default()
446        }
447    }
448}
449
450impl IntoPaginationParams for SearchBuilder<()> {
451    fn into_pagination_params(self) -> PaginationParams {
452        let mut params = PaginationParams::default();
453        if let Some(query) = self.query {
454            let mut search = SearchParams::new(query, self.fields);
455            if self.exact {
456                search = search.with_exact_match(true);
457            }
458            if self.case_sensitive {
459                search = search.with_case_sensitive(true);
460            }
461            params.search = Some(search);
462        }
463        params
464    }
465}
466
467impl IntoPaginationParams for CursorBuilder<()> {
468    fn into_pagination_params(self) -> PaginationParams {
469        PaginationParams {
470            cursor: self.cursor,
471            ..Default::default()
472        }
473    }
474}
475
476impl IntoPaginationParams for PaginatorBuilder {
477    fn into_pagination_params(self) -> PaginationParams {
478        self.params
479    }
480}
481
482//
483// ========================
484//   BACKWARD COMPATIBILITY
485// ========================
486//
487
488/// Legacy builder for backward compatibility
489pub struct PaginatorBuilder {
490    params: PaginationParams,
491}
492
493impl Default for PaginatorBuilder {
494    fn default() -> Self {
495        Self::new()
496    }
497}
498
499impl PaginatorBuilder {
500    pub fn new() -> Self {
501        Self {
502            params: PaginationParams::default(),
503        }
504    }
505
506    pub fn page(mut self, page: u32) -> Self {
507        self.params.page = page.max(1);
508        self
509    }
510
511    pub fn per_page(mut self, per_page: u32) -> Self {
512        self.params.per_page = per_page.clamp(1, 100);
513        self
514    }
515
516    pub fn sort_by(mut self, field: impl Into<String>) -> Self {
517        self.params.sort_by = Some(field.into());
518        self
519    }
520
521    pub fn sort_asc(mut self) -> Self {
522        self.params.sort_direction = Some(SortDirection::Asc);
523        self
524    }
525
526    pub fn sort_desc(mut self) -> Self {
527        self.params.sort_direction = Some(SortDirection::Desc);
528        self
529    }
530
531    pub fn filter(
532        mut self,
533        field: impl Into<String>,
534        operator: FilterOperator,
535        value: FilterValue,
536    ) -> Self {
537        self.params
538            .filters
539            .push(Filter::new(field, operator, value));
540        self
541    }
542
543    pub fn filter_eq(mut self, field: impl Into<String>, value: FilterValue) -> Self {
544        self.params
545            .filters
546            .push(Filter::new(field, FilterOperator::Eq, value));
547        self
548    }
549
550    pub fn filter_ne(mut self, field: impl Into<String>, value: FilterValue) -> Self {
551        self.params
552            .filters
553            .push(Filter::new(field, FilterOperator::Ne, value));
554        self
555    }
556
557    pub fn filter_gt(mut self, field: impl Into<String>, value: FilterValue) -> Self {
558        self.params
559            .filters
560            .push(Filter::new(field, FilterOperator::Gt, value));
561        self
562    }
563
564    pub fn filter_lt(mut self, field: impl Into<String>, value: FilterValue) -> Self {
565        self.params
566            .filters
567            .push(Filter::new(field, FilterOperator::Lt, value));
568        self
569    }
570
571    pub fn filter_gte(mut self, field: impl Into<String>, value: FilterValue) -> Self {
572        self.params
573            .filters
574            .push(Filter::new(field, FilterOperator::Gte, value));
575        self
576    }
577
578    pub fn filter_lte(mut self, field: impl Into<String>, value: FilterValue) -> Self {
579        self.params
580            .filters
581            .push(Filter::new(field, FilterOperator::Lte, value));
582        self
583    }
584
585    pub fn filter_like(mut self, field: impl Into<String>, pattern: impl Into<String>) -> Self {
586        self.params.filters.push(Filter::new(
587            field,
588            FilterOperator::Like,
589            FilterValue::String(pattern.into()),
590        ));
591        self
592    }
593
594    pub fn filter_ilike(mut self, field: impl Into<String>, pattern: impl Into<String>) -> Self {
595        self.params.filters.push(Filter::new(
596            field,
597            FilterOperator::ILike,
598            FilterValue::String(pattern.into()),
599        ));
600        self
601    }
602
603    pub fn filter_in(mut self, field: impl Into<String>, values: Vec<FilterValue>) -> Self {
604        self.params.filters.push(Filter::new(
605            field,
606            FilterOperator::In,
607            FilterValue::Array(values),
608        ));
609        self
610    }
611
612    pub fn filter_between(
613        mut self,
614        field: impl Into<String>,
615        min: FilterValue,
616        max: FilterValue,
617    ) -> Self {
618        self.params.filters.push(Filter::new(
619            field,
620            FilterOperator::Between,
621            FilterValue::Array(vec![min, max]),
622        ));
623        self
624    }
625
626    pub fn filter_is_null(mut self, field: impl Into<String>) -> Self {
627        self.params.filters.push(Filter::new(
628            field,
629            FilterOperator::IsNull,
630            FilterValue::Null,
631        ));
632        self
633    }
634
635    pub fn filter_is_not_null(mut self, field: impl Into<String>) -> Self {
636        self.params.filters.push(Filter::new(
637            field,
638            FilterOperator::IsNotNull,
639            FilterValue::Null,
640        ));
641        self
642    }
643
644    pub fn search(mut self, query: impl Into<String>, fields: Vec<String>) -> Self {
645        self.params.search = Some(SearchParams::new(query, fields));
646        self
647    }
648
649    pub fn search_exact(mut self, query: impl Into<String>, fields: Vec<String>) -> Self {
650        self.params.search = Some(SearchParams::new(query, fields).with_exact_match(true));
651        self
652    }
653
654    pub fn search_case_sensitive(mut self, query: impl Into<String>, fields: Vec<String>) -> Self {
655        self.params.search = Some(SearchParams::new(query, fields).with_case_sensitive(true));
656        self
657    }
658
659    pub fn disable_total_count(mut self) -> Self {
660        self.params.disable_total_count = true;
661        self
662    }
663
664    pub fn cursor(
665        mut self,
666        field: impl Into<String>,
667        value: CursorValue,
668        direction: CursorDirection,
669    ) -> Self {
670        self.params.cursor = Some(Cursor::new(field.into(), value, direction));
671        self
672    }
673
674    pub fn cursor_after(mut self, field: impl Into<String>, value: CursorValue) -> Self {
675        self.params.cursor = Some(Cursor::new(field.into(), value, CursorDirection::After));
676        self
677    }
678
679    pub fn cursor_before(mut self, field: impl Into<String>, value: CursorValue) -> Self {
680        self.params.cursor = Some(Cursor::new(field.into(), value, CursorDirection::Before));
681        self
682    }
683
684    pub fn cursor_from_encoded(mut self, encoded: &str) -> Result<Self, String> {
685        self.params.cursor = Some(Cursor::decode(encoded)?);
686        Ok(self)
687    }
688
689    pub fn build(self) -> PaginationParams {
690        self.params
691    }
692}