ultrafast_mcp_core/utils/
pagination.rs1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6pub struct Cursor {
7 pub value: String,
9 #[serde(skip_serializing_if = "Option::is_none")]
11 pub metadata: Option<HashMap<String, serde_json::Value>>,
12}
13
14impl Cursor {
15 pub fn new(value: impl Into<String>) -> Self {
17 Self {
18 value: value.into(),
19 metadata: None,
20 }
21 }
22
23 pub fn with_metadata(
25 value: impl Into<String>,
26 metadata: HashMap<String, serde_json::Value>,
27 ) -> Self {
28 Self {
29 value: value.into(),
30 metadata: Some(metadata),
31 }
32 }
33
34 pub fn value(&self) -> &str {
36 &self.value
37 }
38
39 pub fn metadata(&self) -> Option<&HashMap<String, serde_json::Value>> {
41 self.metadata.as_ref()
42 }
43}
44
45#[derive(Debug, Clone, Default, Serialize, Deserialize)]
47pub struct PaginationParams {
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub limit: Option<u32>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub cursor: Option<Cursor>,
54}
55
56impl PaginationParams {
57 pub fn new() -> Self {
59 Self::default()
60 }
61
62 pub fn with_limit(mut self, limit: u32) -> Self {
64 self.limit = Some(limit);
65 self
66 }
67
68 pub fn with_cursor(mut self, cursor: Cursor) -> Self {
70 self.cursor = Some(cursor);
71 self
72 }
73
74 pub fn effective_limit(&self, default: u32) -> u32 {
76 self.limit.unwrap_or(default)
77 }
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct PaginationInfo {
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub total: Option<u64>,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub next_cursor: Option<Cursor>,
89 pub has_more: bool,
91}
92
93impl PaginationInfo {
94 pub fn no_more() -> Self {
96 Self {
97 total: None,
98 next_cursor: None,
99 has_more: false,
100 }
101 }
102
103 pub fn with_next(cursor: Cursor) -> Self {
105 Self {
106 total: None,
107 next_cursor: Some(cursor),
108 has_more: true,
109 }
110 }
111
112 pub fn with_total(total: u64, next_cursor: Option<Cursor>) -> Self {
114 Self {
115 total: Some(total),
116 next_cursor: next_cursor.clone(),
117 has_more: next_cursor.is_some(),
118 }
119 }
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct PaginatedList<T> {
125 pub items: Vec<T>,
127 #[serde(flatten)]
129 pub pagination: PaginationInfo,
130}
131
132impl<T> PaginatedList<T> {
133 pub fn new(items: Vec<T>, pagination: PaginationInfo) -> Self {
135 Self { items, pagination }
136 }
137
138 pub fn complete(items: Vec<T>) -> Self {
140 Self {
141 items,
142 pagination: PaginationInfo::no_more(),
143 }
144 }
145
146 pub fn with_next(items: Vec<T>, next_cursor: Cursor) -> Self {
148 Self {
149 items,
150 pagination: PaginationInfo::with_next(next_cursor),
151 }
152 }
153
154 pub fn len(&self) -> usize {
156 self.items.len()
157 }
158
159 pub fn is_empty(&self) -> bool {
161 self.items.is_empty()
162 }
163
164 pub fn has_more(&self) -> bool {
166 self.pagination.has_more
167 }
168
169 pub fn next_cursor(&self) -> Option<&Cursor> {
171 self.pagination.next_cursor.as_ref()
172 }
173}
174
175impl<T> IntoIterator for PaginatedList<T> {
176 type Item = T;
177 type IntoIter = std::vec::IntoIter<T>;
178
179 fn into_iter(self) -> Self::IntoIter {
180 self.items.into_iter()
181 }
182}
183
184impl<T> AsRef<[T]> for PaginatedList<T> {
185 fn as_ref(&self) -> &[T] {
186 &self.items
187 }
188}
189
190pub struct PaginationBuilder<T> {
192 items: Vec<T>,
193 limit: Option<u32>,
194 total: Option<u64>,
195}
196
197impl<T> PaginationBuilder<T> {
198 pub fn new(items: Vec<T>) -> Self {
200 Self {
201 items,
202 limit: None,
203 total: None,
204 }
205 }
206
207 pub fn with_limit(mut self, limit: u32) -> Self {
209 self.limit = Some(limit);
210 self
211 }
212
213 pub fn with_total(mut self, total: u64) -> Self {
215 self.total = Some(total);
216 self
217 }
218
219 pub fn build<F>(self, next_cursor_fn: F) -> PaginatedList<T>
221 where
222 F: FnOnce(&[T]) -> Option<Cursor>,
223 {
224 let has_more = if let Some(limit) = self.limit {
225 self.items.len() > limit as usize
226 } else {
227 false
228 };
229
230 let items = if let Some(limit) = self.limit {
231 self.items.into_iter().take(limit as usize).collect()
232 } else {
233 self.items
234 };
235
236 let next_cursor = if has_more {
237 next_cursor_fn(&items)
238 } else {
239 None
240 };
241
242 let pagination = if let Some(total) = self.total {
243 PaginationInfo::with_total(total, next_cursor)
244 } else if let Some(cursor) = next_cursor {
245 PaginationInfo::with_next(cursor)
246 } else {
247 PaginationInfo::no_more()
248 };
249
250 PaginatedList::new(items, pagination)
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_cursor_creation() {
260 let cursor = Cursor::new("abc123");
261 assert_eq!(cursor.value(), "abc123");
262 assert!(cursor.metadata().is_none());
263
264 let mut metadata = HashMap::new();
265 metadata.insert(
266 "key".to_string(),
267 serde_json::Value::String("value".to_string()),
268 );
269 let cursor = Cursor::with_metadata("def456", metadata.clone());
270 assert_eq!(cursor.value(), "def456");
271 assert_eq!(cursor.metadata(), Some(&metadata));
272 }
273
274 #[test]
275 fn test_pagination_params() {
276 let params = PaginationParams::new()
277 .with_limit(10)
278 .with_cursor(Cursor::new("cursor123"));
279
280 assert_eq!(params.limit, Some(10));
281 assert_eq!(params.cursor.as_ref().unwrap().value(), "cursor123");
282 assert_eq!(params.effective_limit(50), 10);
283
284 let params = PaginationParams::new();
285 assert_eq!(params.effective_limit(50), 50);
286 }
287
288 #[test]
289 fn test_paginated_list() {
290 let items = vec![1, 2, 3, 4, 5];
291 let list = PaginatedList::complete(items.clone());
292
293 assert_eq!(list.len(), 5);
294 assert!(!list.has_more());
295 assert!(list.next_cursor().is_none());
296
297 let list = PaginatedList::with_next(items, Cursor::new("next"));
298 assert!(list.has_more());
299 assert_eq!(list.next_cursor().unwrap().value(), "next");
300 }
301
302 #[test]
303 fn test_pagination_builder() {
304 let items = vec![1, 2, 3, 4, 5];
305 let list = PaginationBuilder::new(items).with_limit(3).build(|items| {
306 if !items.is_empty() {
307 Some(Cursor::new(format!("after_{}", items.last().unwrap())))
308 } else {
309 None
310 }
311 });
312
313 assert_eq!(list.len(), 3);
314 assert!(list.has_more());
315 assert_eq!(list.next_cursor().unwrap().value(), "after_3");
316 }
317}