1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct Cursor(String);
14
15impl Cursor {
16 pub fn new(value: impl Into<String>) -> Self {
17 Self(value.into())
18 }
19
20 pub fn as_str(&self) -> &str {
21 &self.0
22 }
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27#[non_exhaustive]
28pub struct Page<T> {
29 pub items: Vec<T>,
30 pub page_info: PageInfo,
31}
32
33impl<T> Page<T> {
34 pub fn new(items: Vec<T>, page_info: PageInfo) -> Self {
35 Self { items, page_info }
36 }
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
41#[non_exhaustive]
42pub struct PageInfo {
43 pub has_next_page: bool,
44 #[serde(skip_serializing_if = "Option::is_none")]
45 pub end_cursor: Option<Cursor>,
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub total_count: Option<i64>,
48}
49
50impl PageInfo {
51 pub fn last_page() -> Self {
52 Self {
53 has_next_page: false,
54 end_cursor: None,
55 total_count: None,
56 }
57 }
58}
59
60#[cfg(test)]
61#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn cursor_round_trip() {
67 let cursor = Cursor::new("eyJpZCI6NDJ9");
68 let json = serde_json::to_string(&cursor).unwrap();
69 let back: Cursor = serde_json::from_str(&json).unwrap();
70 assert_eq!(cursor, back);
71 }
72
73 #[test]
74 fn page_serialization_shape() {
75 let page = Page::new(
76 vec!["alice", "bob"],
77 PageInfo {
78 has_next_page: true,
79 end_cursor: Some(Cursor::new("abc")),
80 total_count: Some(10),
81 },
82 );
83 let json: serde_json::Value = serde_json::to_value(&page).unwrap();
84 assert_eq!(json["items"], serde_json::json!(["alice", "bob"]));
85 assert_eq!(json["page_info"]["has_next_page"], true);
86 assert_eq!(json["page_info"]["end_cursor"], "abc");
87 assert_eq!(json["page_info"]["total_count"], 10);
88 }
89
90 #[test]
91 fn page_info_skips_none_fields() {
92 let info = PageInfo::last_page();
93 let json: serde_json::Value = serde_json::to_value(&info).unwrap();
94 assert!(!json.as_object().unwrap().contains_key("end_cursor"));
95 assert!(!json.as_object().unwrap().contains_key("total_count"));
96 }
97}