1use axum::{
2 response::{IntoResponse, Response},
3 Json,
4};
5use serde::Serialize;
6
7#[derive(Debug, Clone, Serialize)]
38#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
39pub struct CursorResponse<T: Serialize> {
40 pub data: Vec<T>,
42 #[serde(skip_serializing_if = "Option::is_none")]
45 pub next_cursor: Option<String>,
46 pub has_more: bool,
48}
49
50impl<T: Serialize> IntoResponse for CursorResponse<T> {
51 fn into_response(self) -> Response {
52 Json(self).into_response()
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use serde_json::json;
60
61 #[derive(Serialize)]
62 struct Item {
63 id: String,
64 }
65
66 #[test]
67 fn serializes_with_next_cursor() {
68 let resp = CursorResponse {
69 data: vec![Item { id: "1".into() }],
70 next_cursor: Some("cursor_abc".into()),
71 has_more: true,
72 };
73 let v = serde_json::to_value(&resp).unwrap();
74 assert_eq!(v["data"][0]["id"], "1");
75 assert_eq!(v["next_cursor"], "cursor_abc");
76 assert_eq!(v["has_more"], true);
77 }
78
79 #[test]
80 fn serializes_without_next_cursor_when_none() {
81 let resp: CursorResponse<Item> = CursorResponse {
82 data: vec![],
83 next_cursor: None,
84 has_more: false,
85 };
86 let v = serde_json::to_value(&resp).unwrap();
87 assert!(v.get("next_cursor").is_none());
88 assert_eq!(v["has_more"], false);
89 }
90
91 #[test]
92 fn has_more_false_at_end() {
93 let resp = CursorResponse {
94 data: vec![Item { id: "final".into() }],
95 next_cursor: None,
96 has_more: false,
97 };
98 let v = serde_json::to_value(&resp).unwrap();
99 assert_eq!(v["has_more"], false);
100 assert!(v.get("next_cursor").is_none());
101 }
102
103 #[test]
104 fn has_more_true_with_cursor() {
105 let resp = CursorResponse {
106 data: vec![Item { id: "x".into() }],
107 next_cursor: Some("token".into()),
108 has_more: true,
109 };
110 let v = serde_json::to_value(&resp).unwrap();
111 assert_eq!(v["has_more"], true);
112 assert_eq!(v["next_cursor"], "token");
113 }
114
115 #[test]
116 fn empty_data_with_no_cursor() {
117 let resp: CursorResponse<Item> = CursorResponse {
118 data: vec![],
119 next_cursor: None,
120 has_more: false,
121 };
122 let v = serde_json::to_value(&resp).unwrap();
123 assert_eq!(v["data"], json!([]));
124 assert_eq!(v["has_more"], false);
125 }
126
127 #[test]
128 fn multiple_items() {
129 let resp = CursorResponse {
130 data: vec![
131 Item { id: "1".into() },
132 Item { id: "2".into() },
133 Item { id: "3".into() },
134 ],
135 next_cursor: Some("next".into()),
136 has_more: true,
137 };
138 let v = serde_json::to_value(&resp).unwrap();
139 assert_eq!(v["data"].as_array().unwrap().len(), 3);
140 assert_eq!(v["data"][1]["id"], "2");
141 }
142}