use miniserde::Deserialize;
use serde::Serialize;
use serde_json::Value;
use payjp_types::{AsCursorOpt, List, Object, };
use crate::{RequestBuilder, BlockingClient, PayjpClient, PayjpMethod};
#[doc(hidden)]
pub trait PaginableList: Deserialize {
type Data;
fn into_parts(self) -> ListParts<Self::Data>;
fn from_parts(parts: ListParts<Self::Data>) -> Self;
fn update_params(&mut self, params: &mut Value);
}
#[doc(hidden)]
#[derive(Debug)]
pub struct ListParts<T> {
pub count: Option<u64>,
pub url: String,
pub data: Vec<T>,
pub has_more: bool,
}
impl<T> PaginableList for List<T>
where
T: Object,
List<T>: Deserialize,
{
type Data = T;
fn into_parts(self) -> ListParts<Self::Data> {
ListParts {
count: self.count,
url: self.url,
data: self.data,
has_more: self.has_more,
}
}
fn from_parts(parts: ListParts<Self::Data>) -> Self {
Self {
data: parts.data,
has_more: parts.has_more,
count: parts.count,
url: parts.url,
}
}
fn update_params(&mut self, params: &mut Value) {
if let Some(new_cursor) = self.data.last().and_then(|l| l.id().as_cursor_opt()) {
params["starting_after"] = Value::String(new_cursor.into());
} else {
self.has_more = false;
}
}
}
pub trait PaginationExt {
type Data;
fn into_paginator(self) -> ListPaginator<Self::Data>;
}
impl<T> PaginationExt for List<T>
where
T: Sync + Send + 'static,
List<T>: PaginableList,
{
type Data = List<T>;
fn into_paginator(mut self) -> ListPaginator<List<T>> {
let mut params = Default::default();
self.update_params(&mut params);
ListPaginator { page: self, params }
}
}
#[derive(Debug)]
pub struct ListPaginator<T> {
page: T,
params: Value,
}
impl<T> ListPaginator<List<T>> {
#[doc(hidden)]
pub fn new_list(url: impl Into<String>, params: impl Serialize) -> Self {
let page = List { data: vec![], has_more: true, count: None, url: url.into() };
Self {
page,
params: serde_json::to_value(params)
.expect("all types implement `Serialize` infallibly"),
}
}
}
fn req_builder(url: &str) -> RequestBuilder {
RequestBuilder::new(PayjpMethod::Get, url.trim_start_matches("/v1"))
}
impl<T> ListPaginator<T>
where
T: Sync + Send + 'static + PaginableList,
{
pub fn get_all<C: BlockingClient>(self, client: &C) -> Result<Vec<T::Data>, C::Err> {
let mut data = vec![];
let mut parts = self.page.into_parts();
let mut params = self.params;
loop {
data.append(&mut parts.data);
if !parts.has_more {
break;
}
let req = req_builder(&parts.url).query(¶ms);
let mut next_page: T = req.customize().send_blocking(client)?;
next_page.update_params(&mut params);
parts = next_page.into_parts();
}
Ok(data)
}
pub fn stream<C: PayjpClient + Clone>(
self,
client: &C,
) -> impl futures_util::Stream<Item = Result<T::Data, C::Err>> + Unpin {
let mut page = self.page.into_parts();
page.data.reverse();
let paginator = ListPaginator { page: T::from_parts(page), params: self.params };
Box::pin(futures_util::stream::unfold(
Some((paginator, client.clone())),
Self::unfold_stream,
))
}
async fn unfold_stream<C: PayjpClient + Clone>(
state: Option<(Self, C)>,
) -> Option<(Result<T::Data, C::Err>, Option<(Self, C)>)> {
let (paginator, client) = state?; let mut parts = paginator.page.into_parts();
if let Some(next_val) = parts.data.pop() {
return Some((
Ok(next_val),
Some((Self { page: T::from_parts(parts), params: paginator.params }, client)),
));
}
if !parts.has_more {
return None;
}
let req = req_builder(&parts.url).query(&paginator.params);
match req.customize::<T>().send(&client).await {
Ok(mut next_page) => {
let mut params = paginator.params;
next_page.update_params(&mut params);
let mut parts = next_page.into_parts();
parts.data.reverse();
let next_val = parts.data.pop()?;
Some((Ok(next_val), Some((Self { page: T::from_parts(parts), params }, client))))
}
Err(err) => Some((Err(err), None)), }
}
}