use crate::error::{Error, Result};
use crate::Request;
use reqwest::Request as RawRequest;
use std::collections::HashMap;
pub trait RequestModifier {
fn modify_request(&self, request: &mut RawRequest) -> Result<()>;
}
pub trait Paginator<T, U> {
type Modifier: RequestModifier;
fn modifier(&self, data: U) -> Self::Modifier;
fn next(&self, prev: Option<&U>, res: &T) -> State<U>;
}
pub trait PaginatedRequest: Request {
type Data: Clone;
type Paginator: Paginator<Self::Response, <Self as PaginatedRequest>::Data>;
fn paginator(&self) -> Self::Paginator;
fn initial_page(&self) -> Option<<Self as PaginatedRequest>::Data> {
None
}
}
#[derive(Clone, Debug)]
pub enum State<T> {
Start(Option<T>),
Next(T),
End,
}
impl<T> Default for State<T> {
fn default() -> State<T> {
State::Start(None)
}
}
pub mod query {
use super::*;
#[derive(Debug, Clone)]
pub struct QueryModifier {
pub data: HashMap<String, String>,
}
impl RequestModifier for QueryModifier {
fn modify_request(&self, request: &mut RawRequest) -> Result<()> {
let url = request.url_mut();
let unchanged_queries: Vec<(_, _)> = url
.query_pairs()
.filter(|(k, _)| !self.data.contains_key(k.as_ref()))
.collect();
let mut temp_url = url.clone();
temp_url.set_query(None);
for (key, val) in unchanged_queries {
temp_url.query_pairs_mut().append_pair(&key, &val);
}
for (key, val) in self.data.iter() {
temp_url.query_pairs_mut().append_pair(key, val);
}
url.set_query(temp_url.query());
Ok(())
}
}
pub struct QueryPaginator<T, U> {
f: Box<dyn 'static + Send + Sync + Fn(Option<&U>, &T) -> Option<U>>,
}
impl<T, U> QueryPaginator<T, U> {
pub fn new<F: 'static + Send + Sync + Fn(Option<&U>, &T) -> Option<U>>(f: F) -> Self {
Self { f: Box::new(f) }
}
}
impl<T, U> Paginator<T, U> for QueryPaginator<T, U>
where
U: Into<QueryModifier>,
{
type Modifier = QueryModifier;
fn modifier(&self, data: U) -> QueryModifier {
data.into()
}
fn next(&self, prev: Option<&U>, res: &T) -> State<U> {
let queries = (self.f)(prev, res);
match queries {
Some(queries) => State::Next(queries),
None => State::End,
}
}
}
}
pub mod path {
use super::*;
#[derive(Debug, Clone)]
pub struct PathModifier {
pub data: HashMap<usize, String>,
}
impl RequestModifier for PathModifier {
fn modify_request(&self, request: &mut RawRequest) -> Result<()> {
let url = request.url_mut();
let temp_url = url.clone();
let mut new_segments: Vec<&str> = temp_url
.path_segments()
.ok_or_else(|| Error::Pagination {
msg: "URL cannot be a base".to_string(),
})?
.enumerate()
.map(|(i, x)| self.data.get(&i).map(|val| val.as_str()).unwrap_or(x))
.collect();
let len = new_segments.len();
new_segments.extend(self.data.iter().filter_map(|(i, x)| {
if *i >= len {
Some(x.as_str())
} else {
None
}
}));
let mut path_segments = url.path_segments_mut().map_err(|_| Error::Pagination {
msg: "URL cannot be a base".to_string(),
})?;
path_segments.clear();
path_segments.extend(new_segments.iter());
Ok(())
}
}
pub struct PathPaginator<T, U> {
f: Box<dyn 'static + Send + Sync + Fn(Option<&U>, &T) -> Option<U>>,
}
impl<T, U> PathPaginator<T, U> {
pub fn new<F: 'static + Send + Sync + Fn(Option<&U>, &T) -> Option<U>>(f: F) -> Self {
Self { f: Box::new(f) }
}
}
impl<T, U> Paginator<T, U> for PathPaginator<T, U>
where
U: Into<PathModifier>,
{
type Modifier = PathModifier;
fn modifier(&self, data: U) -> Self::Modifier {
data.into()
}
fn next(&self, prev: Option<&U>, res: &T) -> State<U> {
let path = (self.f)(prev, res);
match path {
Some(path) => State::Next(path),
None => State::End,
}
}
}
}