Skip to main content

gitea_sdk_rs/
pagination.rs

1// Copyright 2026 infinitete. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5//! Pagination options for list endpoints.
6
7/// Options for Gitea API pagination.
8#[derive(Debug, Clone, PartialEq, Eq, Default)]
9pub struct ListOptions {
10    /// Page number. None=server default, Some(0)=disable pagination, Some(n)=explicit page (n >= 1)
11    pub page: Option<i32>,
12    /// Page size. None=server default
13    pub page_size: Option<i32>,
14}
15
16impl ListOptions {
17    /// Apply defaults and return a new value. Idempotent.
18    pub fn with_defaults(&self) -> Self {
19        let page = match self.page {
20            None => Some(1),
21            Some(-1) => Some(0),
22            Some(n) => Some(n),
23        };
24        Self {
25            page,
26            page_size: self.page_size,
27        }
28    }
29}
30
31/// Encode a value as a URL query string (without leading `?`).
32pub trait QueryEncode {
33    /// Returns URL query string (without leading `?`)
34    fn query_encode(&self) -> String;
35}
36
37impl QueryEncode for ListOptions {
38    fn query_encode(&self) -> String {
39        let defaulted = self.with_defaults();
40        let mut out = String::new();
41        if defaulted.page == Some(0) {
42            out.push_str("page=0&limit=0");
43        } else if let Some(page) = defaulted.page {
44            out.push_str(&format!("page={page}"));
45            if let Some(size) = defaulted.page_size {
46                out.push_str(&format!("&limit={size}"));
47            }
48        }
49        out
50    }
51}
52
53/// Trait for types that carry pagination options.
54///
55/// Extended `ListXxxOptions` (Phase 1b) will implement this.
56#[allow(dead_code)]
57pub trait PaginationOptions: QueryEncode {
58    /// Apply defaults, returning a new value without mutation. Idempotent.
59    fn with_defaults(self) -> Self
60    where
61        Self: Sized;
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn test_with_defaults_idempotent() {
70        let d = ListOptions::default();
71        let once = d.with_defaults();
72        let twice = once.with_defaults();
73        assert_eq!(once, twice);
74    }
75
76    #[test]
77    fn test_with_defaults_negative_page() {
78        let opts = ListOptions {
79            page: Some(-1),
80            page_size: Some(10),
81        };
82        assert_eq!(
83            opts.with_defaults(),
84            ListOptions {
85                page: Some(0),
86                page_size: Some(10),
87            }
88        );
89    }
90
91    #[test]
92    fn test_with_defaults_zero_page() {
93        let opts = ListOptions {
94            page: Some(0),
95            page_size: None,
96        };
97        assert_eq!(
98            opts.with_defaults(),
99            ListOptions {
100                page: Some(0),
101                page_size: None,
102            }
103        );
104    }
105
106    #[test]
107    fn test_with_defaults_none_page() {
108        let opts = ListOptions {
109            page: None,
110            page_size: Some(20),
111        };
112        assert_eq!(
113            opts.with_defaults(),
114            ListOptions {
115                page: Some(1),
116                page_size: Some(20),
117            }
118        );
119    }
120
121    #[test]
122    fn test_with_defaults_explicit_page() {
123        let opts = ListOptions {
124            page: Some(3),
125            page_size: Some(50),
126        };
127        assert_eq!(
128            opts.with_defaults(),
129            ListOptions {
130                page: Some(3),
131                page_size: Some(50),
132            }
133        );
134    }
135
136    #[test]
137    fn test_query_encode_normal() {
138        let opts = ListOptions {
139            page: Some(1),
140            page_size: Some(20),
141        };
142        assert_eq!(opts.query_encode(), "page=1&limit=20");
143    }
144
145    #[test]
146    fn test_query_encode_disable() {
147        let opts = ListOptions {
148            page: Some(0),
149            page_size: Some(0),
150        };
151        assert_eq!(opts.query_encode(), "page=0&limit=0");
152    }
153
154    #[test]
155    fn test_query_encode_empty() {
156        let opts = ListOptions::default();
157        assert_eq!(opts.query_encode(), "page=1");
158    }
159}