Skip to main content

lineark_sdk/
pagination.rs

1use serde::{Deserialize, Serialize};
2
3/// Relay-style page info for cursor-based pagination.
4#[derive(Debug, Clone, Default, Serialize, Deserialize)]
5#[serde(rename_all = "camelCase", default)]
6pub struct PageInfo {
7    pub has_next_page: bool,
8    pub end_cursor: Option<String>,
9    pub has_previous_page: Option<bool>,
10    pub start_cursor: Option<String>,
11}
12
13/// A paginated collection of nodes with page info.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase")]
16#[serde(bound(deserialize = "T: serde::de::DeserializeOwned"))]
17pub struct Connection<T> {
18    #[serde(default)]
19    pub nodes: Vec<T>,
20    #[serde(rename = "pageInfo", default)]
21    pub page_info: PageInfo,
22}
23
24#[cfg(test)]
25mod tests {
26    use super::*;
27
28    #[test]
29    fn page_info_deserializes_camel_case() {
30        let json = r#"{
31            "hasNextPage": true,
32            "endCursor": "abc123",
33            "hasPreviousPage": false,
34            "startCursor": "xyz"
35        }"#;
36        let pi: PageInfo = serde_json::from_str(json).unwrap();
37        assert!(pi.has_next_page);
38        assert_eq!(pi.end_cursor, Some("abc123".to_string()));
39        assert_eq!(pi.has_previous_page, Some(false));
40        assert_eq!(pi.start_cursor, Some("xyz".to_string()));
41    }
42
43    #[test]
44    fn page_info_defaults() {
45        let json = r#"{}"#;
46        let pi: PageInfo = serde_json::from_str(json).unwrap();
47        assert!(!pi.has_next_page);
48        assert!(pi.end_cursor.is_none());
49        assert!(pi.has_previous_page.is_none());
50        assert!(pi.start_cursor.is_none());
51    }
52
53    #[test]
54    fn connection_deserializes_with_nodes() {
55        let json = r#"{
56            "nodes": [{"value": 1}, {"value": 2}],
57            "pageInfo": {"hasNextPage": true, "endCursor": "cur"}
58        }"#;
59        let conn: Connection<serde_json::Value> = serde_json::from_str(json).unwrap();
60        assert_eq!(conn.nodes.len(), 2);
61        assert!(conn.page_info.has_next_page);
62        assert_eq!(conn.page_info.end_cursor, Some("cur".to_string()));
63    }
64
65    #[test]
66    fn connection_deserializes_empty_nodes() {
67        let json = r#"{
68            "nodes": [],
69            "pageInfo": {"hasNextPage": false}
70        }"#;
71        let conn: Connection<serde_json::Value> = serde_json::from_str(json).unwrap();
72        assert!(conn.nodes.is_empty());
73        assert!(!conn.page_info.has_next_page);
74    }
75
76    #[test]
77    fn connection_defaults_when_missing() {
78        let json = r#"{}"#;
79        let conn: Connection<serde_json::Value> = serde_json::from_str(json).unwrap();
80        assert!(conn.nodes.is_empty());
81        assert!(!conn.page_info.has_next_page);
82    }
83
84    #[test]
85    fn page_info_serializes_camel_case() {
86        let pi = PageInfo {
87            has_next_page: true,
88            end_cursor: Some("abc".to_string()),
89            has_previous_page: Some(false),
90            start_cursor: None,
91        };
92        let json = serde_json::to_value(&pi).unwrap();
93        assert_eq!(json["hasNextPage"], true);
94        assert_eq!(json["endCursor"], "abc");
95    }
96}