use std::{collections::VecDeque, pin::Pin};
use futures::prelude::*;
use reqwest::RequestBuilder;
use serde::de::DeserializeOwned;
use crate::errors::ExecutionError;
pub(crate) trait PaginatedQuery<T, R>: Clone
where
T: Clone,
R: PaginatedResponse<T>,
{
fn get_offset(&self) -> Option<String>;
fn set_offset(&mut self, value: Option<String>);
fn get_req_builder(&self) -> RequestBuilder;
}
pub(crate) trait PaginatedResponse<T>: Clone + DeserializeOwned
where
T: Clone,
{
fn get_offset(&self) -> Option<String>;
fn get_items(&self) -> VecDeque<T>;
}
struct StreamState<Q, T>
where
Q: Clone,
T: Clone,
{
buffered: VecDeque<T>,
query: Q,
started: bool,
}
macro_rules! handle_stream_err {
($fallible:expr, state = $state:expr) => {
match $fallible {
Ok(value) => value,
Err(err) => {
$state.query.set_offset(None);
return Some((
Err(ExecutionError::from(err)),
StreamState {
buffered: VecDeque::new(),
started: true,
query: $state.query,
},
));
}
}
};
}
pub(crate) fn execute_paginated<T, R>(
query: impl PaginatedQuery<T, R>,
) -> Pin<Box<impl Stream<Item = Result<T, ExecutionError>>>>
where
T: Clone,
R: PaginatedResponse<T>,
{
Box::pin(futures::stream::unfold(
StreamState {
buffered: VecDeque::new(),
query,
started: false,
},
|mut state| async move {
if let Some(value) = state.buffered.pop_front() {
return Some((Ok(value), state));
}
if state.query.get_offset().is_some() || !state.started {
state.started = true;
let http_resp = handle_stream_err!(
handle_stream_err!(state.query.get_req_builder().send().await, state = state)
.error_for_status(),
state = state
);
let deserialized_resp: R =
handle_stream_err!(http_resp.json().await, state = state);
state.buffered = deserialized_resp.get_items();
state.query.set_offset(deserialized_resp.get_offset());
if let Some(value) = state.buffered.pop_front() {
return Some((Ok(value), state));
}
}
None
},
))
}