use std::sync::Arc;
use reqwest::Method;
use serde::de::DeserializeOwned;
use serde::Deserialize;
use crate::error::Error;
use crate::http::HttpClient;
use crate::params::Params;
#[derive(Debug, Clone, Default, Deserialize)]
pub struct PageMeta {
#[serde(default)]
pub current_page: u32,
pub from: Option<u32>,
#[serde(default)]
pub last_page: u32,
#[serde(default)]
pub per_page: u32,
pub to: Option<u32>,
#[serde(default)]
pub total: u64,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct PageLinks {
pub first: Option<String>,
pub last: Option<String>,
pub prev: Option<String>,
pub next: Option<String>,
}
#[derive(Deserialize)]
struct Envelope<T> {
data: Option<Vec<T>>,
meta: Option<PageMeta>,
links: Option<PageLinks>,
}
pub struct Page<T> {
pub data: Vec<T>,
pub meta: PageMeta,
pub links: PageLinks,
http: Arc<HttpClient>,
path: String,
params: Params,
}
impl<T: DeserializeOwned> Page<T> {
pub(crate) fn fetch(http: Arc<HttpClient>, path: &str, params: Params) -> Result<Self, Error> {
let value = http.send(Method::GET, path, Some(¶ms), None)?;
let env: Envelope<T> =
serde_json::from_value(value).map_err(|e| Error::Decode(e.to_string()))?;
Ok(Page {
data: env.data.unwrap_or_default(),
meta: env.meta.unwrap_or_default(),
links: env.links.unwrap_or_default(),
http,
path: path.to_string(),
params,
})
}
pub fn has_more(&self) -> bool {
if self.links.next.as_deref().is_some_and(|s| !s.is_empty()) {
return true;
}
self.meta.current_page != 0 && self.meta.last_page != 0 && self.meta.current_page < self.meta.last_page
}
pub fn next_page(&self) -> Result<Option<Page<T>>, Error> {
if !self.has_more() {
return Ok(None);
}
let params = self.params.with_page(self.meta.current_page + 1);
Page::fetch(self.http.clone(), &self.path, params).map(Some)
}
pub fn auto_paging(self) -> AutoPager<T> {
let next_params = if self.has_more() {
Some(self.params.with_page(self.meta.current_page + 1))
} else {
None
};
AutoPager {
http: self.http,
path: self.path,
next_params,
current: self.data.into_iter(),
}
}
}
pub struct AutoPager<T> {
http: Arc<HttpClient>,
path: String,
next_params: Option<Params>,
current: std::vec::IntoIter<T>,
}
impl<T: DeserializeOwned> Iterator for AutoPager<T> {
type Item = Result<T, Error>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(item) = self.current.next() {
return Some(Ok(item));
}
let params = self.next_params.take()?;
match Page::<T>::fetch(self.http.clone(), &self.path, params) {
Ok(page) => {
self.next_params = if page.has_more() {
Some(page.params.with_page(page.meta.current_page + 1))
} else {
None
};
self.current = page.data.into_iter();
}
Err(e) => return Some(Err(e)),
}
}
}
}