payjp_client_core/
pagination.rs1use miniserde::Deserialize;
2use serde::Serialize;
3use serde_json::Value;
4use payjp_types::{AsCursorOpt, List, Object, };
5
6use crate::{RequestBuilder, BlockingClient, PayjpClient, PayjpMethod};
7
8#[doc(hidden)]
17pub trait PaginableList: Deserialize {
18 type Data;
20
21 fn into_parts(self) -> ListParts<Self::Data>;
23
24 fn from_parts(parts: ListParts<Self::Data>) -> Self;
26
27 fn update_params(&mut self, params: &mut Value);
35}
36
37#[doc(hidden)]
39#[derive(Debug)]
40pub struct ListParts<T> {
41 pub count: Option<u64>,
42 pub url: String,
43 pub data: Vec<T>,
44 pub has_more: bool,
45}
46
47impl<T> PaginableList for List<T>
48where
49 T: Object,
50 List<T>: Deserialize,
51{
52 type Data = T;
53
54 fn into_parts(self) -> ListParts<Self::Data> {
55 ListParts {
56 count: self.count,
57 url: self.url,
58 data: self.data,
59 has_more: self.has_more,
60 }
61 }
62
63 fn from_parts(parts: ListParts<Self::Data>) -> Self {
64 Self {
65 data: parts.data,
66 has_more: parts.has_more,
67 count: parts.count,
68 url: parts.url,
69 }
70 }
71
72 fn update_params(&mut self, params: &mut Value) {
73 if let Some(new_cursor) = self.data.last().and_then(|l| l.id().as_cursor_opt()) {
74 params["starting_after"] = Value::String(new_cursor.into());
75 } else {
76 self.has_more = false;
77 }
78 }
79}
80
81pub trait PaginationExt {
84 type Data;
86
87 fn into_paginator(self) -> ListPaginator<Self::Data>;
90}
91
92impl<T> PaginationExt for List<T>
93where
94 T: Sync + Send + 'static,
95 List<T>: PaginableList,
96{
97 type Data = List<T>;
98
99 fn into_paginator(mut self) -> ListPaginator<List<T>> {
100 let mut params = Default::default();
101 self.update_params(&mut params);
102 ListPaginator { page: self, params }
103 }
104}
105
106#[derive(Debug)]
108pub struct ListPaginator<T> {
109 page: T,
110 params: Value,
111}
112
113impl<T> ListPaginator<List<T>> {
114 #[doc(hidden)]
117 pub fn new_list(url: impl Into<String>, params: impl Serialize) -> Self {
118 let page = List { data: vec![], has_more: true, count: None, url: url.into() };
119 Self {
120 page,
121 params: serde_json::to_value(params)
122 .expect("all types implement `Serialize` infallibly"),
123 }
124 }
125}
126
127fn req_builder(url: &str) -> RequestBuilder {
128 RequestBuilder::new(PayjpMethod::Get, url.trim_start_matches("/v1"))
129}
130
131impl<T> ListPaginator<T>
132where
133 T: Sync + Send + 'static + PaginableList,
134{
135 pub fn get_all<C: BlockingClient>(self, client: &C) -> Result<Vec<T::Data>, C::Err> {
140 let mut data = vec![];
141 let mut parts = self.page.into_parts();
142 let mut params = self.params;
143 loop {
144 data.append(&mut parts.data);
146
147 if !parts.has_more {
148 break;
149 }
150
151 let req = req_builder(&parts.url).query(¶ms);
152 let mut next_page: T = req.customize().send_blocking(client)?;
153 next_page.update_params(&mut params);
154 parts = next_page.into_parts();
155 }
156 Ok(data)
157 }
158
159 pub fn stream<C: PayjpClient + Clone>(
163 self,
164 client: &C,
165 ) -> impl futures_util::Stream<Item = Result<T::Data, C::Err>> + Unpin {
166 let mut page = self.page.into_parts();
168 page.data.reverse();
169 let paginator = ListPaginator { page: T::from_parts(page), params: self.params };
170
171 Box::pin(futures_util::stream::unfold(
172 Some((paginator, client.clone())),
173 Self::unfold_stream,
174 ))
175 }
176
177 async fn unfold_stream<C: PayjpClient + Clone>(
179 state: Option<(Self, C)>,
180 ) -> Option<(Result<T::Data, C::Err>, Option<(Self, C)>)> {
181 let (paginator, client) = state?; let mut parts = paginator.page.into_parts();
183 if let Some(next_val) = parts.data.pop() {
184 return Some((
186 Ok(next_val),
187 Some((Self { page: T::from_parts(parts), params: paginator.params }, client)),
188 ));
189 }
190
191 if !parts.has_more {
193 return None;
194 }
195
196 let req = req_builder(&parts.url).query(&paginator.params);
197 match req.customize::<T>().send(&client).await {
198 Ok(mut next_page) => {
199 let mut params = paginator.params;
200 next_page.update_params(&mut params);
201
202 let mut parts = next_page.into_parts();
203
204 parts.data.reverse();
206
207 let next_val = parts.data.pop()?;
208
209 Some((Ok(next_val), Some((Self { page: T::from_parts(parts), params }, client))))
211 }
212 Err(err) => Some((Err(err), None)), }
214 }
215}