use hyperx::header::TypedHeaders;
use snafu::ResultExt;
use url::Url;
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct Page<T> {
pub items: Vec<T>,
pub incomplete_results: Option<bool>,
pub total_count: Option<u64>,
pub next: Option<Url>,
pub prev: Option<Url>,
pub first: Option<Url>,
pub last: Option<Url>,
}
impl<T> Page<T> {
pub fn take_items(&mut self) -> Vec<T> {
std::mem::replace(&mut self.items, Vec::new())
}
pub fn number_of_pages(&self) -> Option<u32> {
self.last.as_ref().and_then(|url| {
url.query_pairs()
.filter_map(|(k, v)| {
if k == "page" {
Some(v).and_then(|v| v.parse().ok())
} else {
None
}
})
.next()
})
}
}
impl<T> Default for Page<T> {
fn default() -> Self {
Self {
items: Vec::new(),
incomplete_results: None,
total_count: None,
next: None,
prev: None,
first: None,
last: None,
}
}
}
impl<T> IntoIterator for Page<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.items.into_iter()
}
}
#[async_trait::async_trait]
impl<T: serde::de::DeserializeOwned> crate::FromResponse for Page<T> {
async fn from_response(response: reqwest::Response) -> crate::Result<Self> {
let (first, prev, next, last) = get_links(&response)?;
let json: serde_json::Value = response.json().await.context(crate::error::Http)?;
if json.is_array() {
Ok(Self {
items: serde_json::from_value(json).context(crate::error::Serde)?,
incomplete_results: None,
total_count: None,
next,
prev,
first,
last,
})
} else {
Ok(Self {
items: serde_json::from_value(json.get("items").cloned().unwrap())
.context(crate::error::Serde)?,
incomplete_results: json
.get("incomplete_results")
.and_then(serde_json::Value::as_bool),
total_count: json.get("total_count").and_then(serde_json::Value::as_u64),
next,
prev,
first,
last,
})
}
}
}
fn get_links(
response: &reqwest::Response,
) -> crate::Result<(Option<Url>, Option<Url>, Option<Url>, Option<Url>)> {
let mut first = None;
let mut prev = None;
let mut next = None;
let mut last = None;
if let Ok(link_header) = response.headers().decode::<hyperx::header::Link>() {
for value in link_header.values() {
if let Some(relations) = value.rel() {
if relations.contains(&hyperx::header::RelationType::Next) {
next = Some(Url::parse(value.link()).context(crate::error::Url)?);
}
if relations.contains(&hyperx::header::RelationType::Prev) {
prev = Some(Url::parse(value.link()).context(crate::error::Url)?);
}
if relations.contains(&hyperx::header::RelationType::First) {
first = Some(Url::parse(value.link()).context(crate::error::Url)?)
}
if relations.contains(&hyperx::header::RelationType::Last) {
last = Some(Url::parse(value.link()).context(crate::error::Url)?)
}
}
}
}
Ok((first, prev, next, last))
}