1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
//! Module for handling unresolved URLs returned by the scryfall api
//!
//! Some fields of the scryfall api have URLs referring to queries that can be
//! run to obtain more information. This module abstracts the work of fetching
//! that data.
use std::convert::TryFrom;
use std::marker::PhantomData;
use httpstatus::StatusCode;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use url::Url;
use crate::error::Error;
use crate::list::{List, ListIter};
/// An unresolved URI returned by the Scryfall API, or generated by this crate.
///
/// The `fetch` method handles requesting the resource from the API endpoint,
/// and deserializing it into a `T` object. If the type parameter is
/// [`List`][crate::list::List]`<_>`, then additional methods `fetch_iter`
/// and `fetch_all` are available, giving access to objects from all pages
/// of the collection.
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Hash, Debug)]
#[serde(transparent)]
pub struct Uri<T> {
url: Url,
_marker: PhantomData<fn() -> T>,
}
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: DeserializeOwned> Uri<T> {
/// Fetches a resource from the Scryfall API and deserializes it into a type
/// `T`.
///
/// # Example
/// ```rust
/// # use std::convert::TryFrom;
/// #
/// # use scryfall::card::Card;
/// # use scryfall::uri::Uri;
/// # tokio_test::block_on(async {
/// let uri =
/// Uri::<Card>::try_from("https://api.scryfall.com/cards/named?exact=Lightning+Bolt").unwrap();
/// let bolt = uri.fetch().await.unwrap();
/// assert_eq!(bolt.mana_cost, Some("{R}".to_string()));
/// # })
/// ```
pub async fn fetch(&self) -> crate::Result<T> {
match self.fetch_raw().await {
Ok(response) => match response.status().as_u16() {
200..=299 => Ok(response.json().await?),
status => Err(Error::HttpError(StatusCode::from(status))),
},
Err(e) => Err(e),
}
}
pub(crate) async fn fetch_raw(&self) -> crate::Result<reqwest::Response> {
match reqwest::get(self.url.clone()).await {
Ok(response) => match response.status().as_u16() {
400..=599 => Err(Error::ScryfallError(response.json().await?)),
_ => Ok(response),
},
Err(e) => Err(Error::ReqwestError(e.into(), self.url.to_string())),
}
}
}
impl<T: DeserializeOwned + Send + Sync + Unpin> Uri<List<T>> {
/// Lazily iterate over items from all pages of a list. Following pages are
/// requested once the previous page has been exhausted.
///
/// # Example
/// ```rust
/// # use std::convert::TryFrom;
/// #
/// # use scryfall::Card;
/// # use scryfall::list::List;
/// # use scryfall::uri::Uri;
/// use futures::stream::StreamExt;
/// use futures::future;
/// # tokio_test::block_on(async {
/// let uri = Uri::<List<Card>>::try_from("https://api.scryfall.com/cards/search?q=zurgo").unwrap();
/// assert!(
/// uri.fetch_iter()
/// .await
/// .unwrap()
/// .into_stream()
/// .map(Result::unwrap)
/// .filter(|c| future::ready(c.name.contains("Bellstriker")))
/// .collect::<Vec<_>>()
/// .await
/// .len()
/// > 0
/// );
/// # })
/// ```
///
/// ```rust
/// # use std::convert::TryFrom;
/// #
/// # use scryfall::Card;
/// # use scryfall::list::List;
/// # use scryfall::uri::Uri;
/// use futures::stream::StreamExt;
/// use futures::future;
/// # tokio_test::block_on(async {
/// let uri = Uri::<List<Card>>::try_from("https://api.scryfall.com/cards/search?q=zurgo").unwrap();
/// assert!(
/// uri.fetch_iter()
/// .await
/// .unwrap()
/// .into_stream_buffered(10)
/// .map(Result::unwrap)
/// .filter(|c| future::ready(c.name.contains("Bellstriker")))
/// .collect::<Vec<_>>()
/// .await
/// .len()
/// > 0
/// );
/// # })
/// ```
pub async fn fetch_iter(&self) -> crate::Result<ListIter<T>> {
Ok(self.fetch().await?.into_list_iter())
}
/// Eagerly fetch items from all pages of a list. If any of the pages fail
/// to load, returns an error.
///
/// # Example
/// ```rust
/// # use std::convert::TryFrom;
/// #
/// # use scryfall::Card;
/// # use scryfall::list::List;
/// # use scryfall::uri::Uri;
/// # tokio_test::block_on(async {
/// let uri =
/// Uri::<List<Card>>::try_from("https://api.scryfall.com/cards/search?q=e:ddu&unique=prints")
/// .unwrap();
/// assert_eq!(uri.fetch_all().await.unwrap().len(), 76);
/// # })
/// ```
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)
}
}