use std::collections::VecDeque;
use std::fmt;
use std::ops::Deref;
use std::pin::Pin;
use futures_util::Stream;
use reqwest::Method;
use serde_json::Value;
use crate::client::Client;
use crate::error::Result;
use crate::models::{boolean_or, integer};
pub type ItemStream<T> = Pin<Box<dyn Stream<Item = Result<T>> + Send + 'static>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Order {
Asc,
Desc,
}
impl Order {
pub fn as_str(self) -> &'static str {
match self {
Order::Asc => "ASC",
Order::Desc => "DESC",
}
}
}
impl fmt::Display for Order {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct Page<T> {
pub items: Vec<T>,
pub page_number: u32,
pub page_size: u32,
pub total_pages: u64,
pub total_elements: u64,
pub first: bool,
pub last: bool,
pub raw: Value,
}
impl<T> Page<T> {
pub fn has_next(&self) -> bool {
!self.last && !self.items.is_empty()
}
pub fn into_vec(self) -> Vec<T> {
self.items
}
pub(crate) fn from_value(raw: Value, parse: fn(Value) -> T) -> Self {
let items = raw
.get("content")
.and_then(Value::as_array)
.into_iter()
.flatten()
.filter(|item| item.is_object())
.cloned()
.map(parse)
.collect();
Page {
items,
page_number: count(&raw, "pageNumber"),
page_size: count(&raw, "pageSize"),
total_pages: count(&raw, "totalPages"),
total_elements: count(&raw, "totalElements"),
first: boolean_or(&raw, "first", true),
last: boolean_or(&raw, "last", true),
raw,
}
}
}
impl<T> Deref for Page<T> {
type Target = [T];
fn deref(&self) -> &[T] {
&self.items
}
}
impl<T> IntoIterator for Page<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.items.into_iter()
}
}
impl<'a, T> IntoIterator for &'a Page<T> {
type Item = &'a T;
type IntoIter = std::slice::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.items.iter()
}
}
fn count<N: TryFrom<i64> + Default>(raw: &Value, key: &str) -> N {
integer(raw, key)
.and_then(|value| N::try_from(value).ok())
.unwrap_or_default()
}
#[derive(Debug, Clone, Default)]
pub(crate) struct Paging {
pub(crate) page: u32,
pub(crate) page_size: Option<u32>,
pub(crate) sort: Option<String>,
pub(crate) order: Option<Order>,
}
macro_rules! paging_setters {
() => {
pub fn page(mut self, page: u32) -> Self {
self.paging.page = page;
self
}
pub fn page_size(mut self, page_size: u32) -> Self {
self.paging.page_size = Some(page_size);
self
}
pub fn sort(mut self, sort: impl Into<String>) -> Self {
self.paging.sort = Some(sort.into());
self
}
pub fn order(mut self, order: $crate::Order) -> Self {
self.paging.order = Some(order);
self
}
};
}
pub(crate) use paging_setters;
pub(crate) struct ListCall<T> {
pub(crate) path: &'static str,
pub(crate) filters: Vec<(&'static str, String)>,
pub(crate) paging: Paging,
pub(crate) parse: fn(Value) -> T,
}
impl<T> ListCall<T> {
fn query(&self, number: u32) -> Vec<(&'static str, String)> {
let mut query = self.filters.clone();
query.push(("pageNumber", number.to_string()));
if let Some(page_size) = self.paging.page_size {
query.push(("pageSize", page_size.to_string()));
}
if let Some(sort) = &self.paging.sort {
query.push(("sort", sort.clone()));
}
if let Some(order) = self.paging.order {
query.push(("order", order.as_str().to_owned()));
}
query
}
async fn fetch(&self, client: &Client, number: u32) -> Result<Page<T>> {
let payload = client
.request(Method::GET, self.path, &self.query(number), None)
.await?;
Ok(Page::from_value(
payload.filter(Value::is_object).unwrap_or(Value::Null),
self.parse,
))
}
pub(crate) async fn page(self, client: &Client) -> Result<Page<T>> {
self.fetch(client, self.paging.page).await
}
}
impl<T: Send + 'static> ListCall<T> {
pub(crate) fn walk(self, client: &Client) -> ItemStream<T> {
let state = Walk {
next: Some(self.paging.page),
buffer: VecDeque::new(),
client: client.clone(),
call: self,
};
Box::pin(futures_util::stream::unfold(
state,
|mut state| async move {
loop {
if let Some(item) = state.buffer.pop_front() {
return Some((Ok(item), state));
}
let number = state.next?;
match state.call.fetch(&state.client, number).await {
Ok(page) => {
state.next = if page.has_next() {
number.checked_add(1)
} else {
None
};
state.buffer = page.items.into();
}
Err(error) => {
state.next = None;
return Some((Err(error), state));
}
}
}
},
))
}
}
struct Walk<T> {
next: Option<u32>,
buffer: VecDeque<T>,
client: Client,
call: ListCall<T>,
}
pub(crate) fn filter(
filters: &mut Vec<(&'static str, String)>,
name: &'static str,
value: Option<impl ToString>,
) {
if let Some(value) = value {
filters.push((name, value.to_string()));
}
}