datadog_api/
pagination.rs

1//! Pagination utilities for Datadog API responses
2//!
3//! Provides helpers for working with paginated API endpoints.
4
5use serde::{Deserialize, Serialize};
6
7/// Page-based pagination parameters
8#[derive(Debug, Clone, Serialize)]
9pub struct PageParams {
10    /// Number of items per page
11    #[serde(rename = "page[size]")]
12    pub page_size: i32,
13    /// Page offset (0-indexed)
14    #[serde(rename = "page[offset]", skip_serializing_if = "Option::is_none")]
15    pub page_offset: Option<i64>,
16}
17
18impl PageParams {
19    /// Create new page parameters with the given size
20    pub fn new(page_size: i32) -> Self {
21        Self {
22            page_size,
23            page_offset: None,
24        }
25    }
26
27    /// Create page parameters with size and offset
28    pub fn with_offset(page_size: i32, offset: i64) -> Self {
29        Self {
30            page_size,
31            page_offset: Some(offset),
32        }
33    }
34}
35
36impl Default for PageParams {
37    fn default() -> Self {
38        Self::new(100)
39    }
40}
41
42/// Cursor-based pagination parameters
43#[derive(Debug, Clone, Serialize)]
44pub struct CursorParams {
45    /// Number of items per page
46    #[serde(rename = "page[limit]")]
47    pub limit: i32,
48    /// Cursor for next page
49    #[serde(rename = "page[cursor]", skip_serializing_if = "Option::is_none")]
50    pub cursor: Option<String>,
51}
52
53impl CursorParams {
54    /// Create new cursor parameters with the given limit
55    pub fn new(limit: i32) -> Self {
56        Self {
57            limit,
58            cursor: None,
59        }
60    }
61
62    /// Create cursor parameters with limit and cursor
63    pub fn with_cursor(limit: i32, cursor: String) -> Self {
64        Self {
65            limit,
66            cursor: Some(cursor),
67        }
68    }
69}
70
71impl Default for CursorParams {
72    fn default() -> Self {
73        Self::new(100)
74    }
75}
76
77/// Pagination metadata from API responses
78#[derive(Debug, Clone, Deserialize, Default)]
79pub struct PaginationMeta {
80    /// Total count of items (if available)
81    #[serde(default)]
82    pub total_count: Option<i64>,
83    /// Total number of pages (if available)
84    #[serde(default)]
85    pub total_pages: Option<i64>,
86    /// Offset for the next page (if available)
87    #[serde(default)]
88    pub next_offset: Option<i64>,
89    /// Cursor for the next page (if available)
90    #[serde(default)]
91    pub next_cursor: Option<String>,
92}
93
94impl PaginationMeta {
95    /// Check if there are more pages
96    pub fn has_next(&self) -> bool {
97        self.next_offset.is_some() || self.next_cursor.is_some()
98    }
99}
100
101/// Result of a paginated request
102#[derive(Debug, Clone)]
103pub struct PaginatedResponse<T> {
104    /// Items from this page
105    pub items: Vec<T>,
106    /// Pagination metadata
107    pub meta: PaginationMeta,
108}
109
110impl<T> PaginatedResponse<T> {
111    /// Create a new paginated response
112    pub fn new(items: Vec<T>, meta: PaginationMeta) -> Self {
113        Self { items, meta }
114    }
115
116    /// Check if there are more pages
117    pub fn has_next(&self) -> bool {
118        self.meta.has_next()
119    }
120
121    /// Get the total count if available
122    pub fn total_count(&self) -> Option<i64> {
123        self.meta.total_count
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn test_page_params_default() {
133        let params = PageParams::default();
134        assert_eq!(params.page_size, 100);
135        assert!(params.page_offset.is_none());
136    }
137
138    #[test]
139    fn test_page_params_with_offset() {
140        let params = PageParams::with_offset(50, 100);
141        assert_eq!(params.page_size, 50);
142        assert_eq!(params.page_offset, Some(100));
143    }
144
145    #[test]
146    fn test_cursor_params_default() {
147        let params = CursorParams::default();
148        assert_eq!(params.limit, 100);
149        assert!(params.cursor.is_none());
150    }
151
152    #[test]
153    fn test_cursor_params_with_cursor() {
154        let params = CursorParams::with_cursor(50, "abc123".to_string());
155        assert_eq!(params.limit, 50);
156        assert_eq!(params.cursor, Some("abc123".to_string()));
157    }
158
159    #[test]
160    fn test_pagination_meta_has_next() {
161        let meta_with_offset = PaginationMeta {
162            next_offset: Some(100),
163            ..Default::default()
164        };
165        assert!(meta_with_offset.has_next());
166
167        let meta_with_cursor = PaginationMeta {
168            next_cursor: Some("abc".to_string()),
169            ..Default::default()
170        };
171        assert!(meta_with_cursor.has_next());
172
173        let meta_empty = PaginationMeta::default();
174        assert!(!meta_empty.has_next());
175    }
176
177    #[test]
178    fn test_paginated_response() {
179        let response = PaginatedResponse::new(
180            vec![1, 2, 3],
181            PaginationMeta {
182                total_count: Some(100),
183                next_offset: Some(3),
184                ..Default::default()
185            },
186        );
187
188        assert_eq!(response.items.len(), 3);
189        assert!(response.has_next());
190        assert_eq!(response.total_count(), Some(100));
191    }
192}