1use serde::{Deserialize, Serialize};
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23#[non_exhaustive]
24pub struct Paginated<T> {
25 pub data: Vec<T>,
27 #[serde(default)]
29 pub has_more: bool,
30 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub first_id: Option<String>,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
35 pub last_id: Option<String>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42#[non_exhaustive]
43pub struct PaginatedNextPage<T> {
44 pub data: Vec<T>,
46 #[serde(default)]
49 pub has_more: bool,
50 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub next_page: Option<String>,
53}
54
55impl<T> PaginatedNextPage<T> {
56 pub fn next_cursor(&self) -> Option<&str> {
59 self.next_page.as_deref()
60 }
61
62 pub fn is_empty(&self) -> bool {
64 self.data.is_empty()
65 }
66}
67
68impl<T> Paginated<T> {
69 pub fn next_after(&self) -> Option<&str> {
73 if self.has_more {
74 self.last_id.as_deref()
75 } else {
76 None
77 }
78 }
79
80 pub fn next_before(&self) -> Option<&str> {
83 self.first_id.as_deref()
84 }
85
86 pub fn is_empty(&self) -> bool {
88 self.data.is_empty()
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use pretty_assertions::assert_eq;
96 use serde_json::json;
97
98 #[test]
99 fn paginated_round_trips_full_envelope() {
100 let p = Paginated {
101 data: vec!["a".to_owned(), "b".to_owned()],
102 has_more: true,
103 first_id: Some("first".into()),
104 last_id: Some("last".into()),
105 };
106 let v = serde_json::to_value(&p).unwrap();
107 assert_eq!(
108 v,
109 json!({
110 "data": ["a", "b"],
111 "has_more": true,
112 "first_id": "first",
113 "last_id": "last"
114 })
115 );
116 let parsed: Paginated<String> = serde_json::from_value(v).unwrap();
117 assert_eq!(parsed, p);
118 }
119
120 #[test]
121 fn paginated_tolerates_missing_optional_fields() {
122 let raw = json!({"data": [1, 2, 3]});
123 let p: Paginated<i32> = serde_json::from_value(raw).unwrap();
124 assert_eq!(p.data, vec![1, 2, 3]);
125 assert!(!p.has_more);
126 assert_eq!(p.first_id, None);
127 assert_eq!(p.last_id, None);
128 }
129
130 #[test]
131 fn next_after_returns_last_id_only_when_more_pages() {
132 let p = Paginated::<i32> {
133 data: vec![1],
134 has_more: true,
135 first_id: Some("f".into()),
136 last_id: Some("l".into()),
137 };
138 assert_eq!(p.next_after(), Some("l"));
139
140 let p_done = Paginated::<i32> {
141 has_more: false,
142 ..p
143 };
144 assert_eq!(p_done.next_after(), None);
145 }
146}