use std::convert::TryFrom;
use std::marker::PhantomData;
use httpstatus::StatusCode;
use reqwest::header::{self, HeaderValue};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::error::Error;
use crate::list::{List, ListIter};
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(test, serde(deny_unknown_fields))]
#[serde(transparent)]
pub struct Uri<T> {
url: Url,
_marker: PhantomData<fn() -> T>,
}
impl<T> std::fmt::Debug for Uri<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Uri")
.field("url", &self.url)
.field("type", &std::any::type_name::<T>())
.finish()
}
}
impl<T> TryFrom<&str> for Uri<T> {
type Error = crate::error::Error;
fn try_from(url: &str) -> Result<Self, Self::Error> {
Ok(Uri::from(Url::parse(url)?))
}
}
impl<T> From<Url> for Uri<T> {
fn from(url: Url) -> Self {
Uri {
url,
_marker: PhantomData,
}
}
}
impl<T> Uri<T> {
pub fn into_inner(self) -> Url {
self.url
}
pub fn inner(&self) -> &Url {
&self.url
}
pub fn as_str(&self) -> &str {
self.url.as_str()
}
}
impl<T> AsRef<Url> for Uri<T> {
fn as_ref(&self) -> &Url {
self.inner()
}
}
impl<T> AsRef<str> for Uri<T> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
fn create_client() -> reqwest::Client {
reqwest::Client::builder()
.default_headers(
[
(header::ACCEPT, HeaderValue::from_static("*/*")),
(header::USER_AGENT, HeaderValue::from_static("scryfall-rs")),
]
.into_iter()
.collect(),
)
.build()
.unwrap()
}
#[cfg(test)]
use create_client as client;
#[cfg(not(test))]
fn client() -> &'static reqwest::Client {
use std::sync::OnceLock;
static CLIENT: OnceLock<reqwest::Client> = OnceLock::new();
CLIENT.get_or_init(create_client)
}
impl<T: DeserializeOwned> Uri<T> {
pub async fn fetch(&self) -> crate::Result<T> {
match self.fetch_raw().await {
Ok(response) => match response.status().as_u16() {
200..=299 => response.json().await.map_err(|e| Error::ReqwestError {
error: e.into(),
url: self.url.clone(),
}),
status => Err(Error::HttpError(StatusCode::from(status))),
},
Err(e) => Err(e),
}
}
pub(crate) async fn fetch_raw(&self) -> crate::Result<reqwest::Response> {
match client().get(self.url.clone()).send().await {
Ok(response) => match response.status().as_u16() {
400..=599 => Err(Error::ScryfallError(response.json().await.map_err(
|e| Error::ReqwestError {
error: e.into(),
url: self.url.clone(),
},
)?)),
_ => Ok(response),
},
Err(e) => Err(Error::ReqwestError {
error: e.into(),
url: self.url.clone(),
}),
}
}
}
impl<T: DeserializeOwned + Send + Sync + Unpin> Uri<List<T>> {
pub async fn fetch_iter(&self) -> crate::Result<ListIter<T>> {
Ok(self.fetch().await?.into_list_iter())
}
pub async fn fetch_all(&self) -> crate::Result<Vec<T>> {
let mut items = vec![];
let mut next_page = Some(self.fetch().await?);
while let Some(page) = next_page {
items.extend(page.data.into_iter());
next_page = match page.next_page {
Some(uri) => Some(uri.fetch().await?),
None => None,
};
}
Ok(items)
}
}