41
loading...
This website collects cookies to deliver better user experience
TL;DR: This is a post for beginners where I show:
cards.filter().types("creature").colors("red").name("dragon")
...);GET
methods). The result can be found here.
use mtgsdk
instead of use mtgsdk::mtgsdk
mtgsdk
, which I found is not how the cool kids do it.If you want to see for yourself, fork/download the repository and type cargo doc --open
lib.rs
. Otherwise, you have to create a folder with your single module name, create a mod.rs
file in it and use the mod
and pub mod
inside it, declaring only the folder name within lib.rs
(in this case, lib.rs
would only have pub mod mtgsdk;
.GET
methods, and there's not much to talk about how reqwest handles it, for it is pretty much just passing a URL as you would do in a curl
.I am not saying that this is all that reqwest does; it is not. I am saying that for this API we don't actually need anything else that accessing the URL and parsing the Json (more on this later).
reqwest::get(url)
inside every module, I created a query builder that receives an url
and returns a Result<T, StatusCode>
where T is a struct containing the data for the various calls (cards, formats, etc.). StatusCode
, so the developer using this crate would easily handle the errors. Here is the code with some additional comments.async fn build<T>(url: String) -> Result<T, StatusCode>
where
//This is a requirement for Serde; I will talk about it below.
T: DeserializeOwned,
{
let response = reqwest::get(url).await;
// I am using match instead of "if let" or "?"
// to make what's happening here crystal clear
match &response {
Ok(r) => {
if r.status() != StatusCode::OK {
return Err(r.status());
}
}
Err(e) => {
if e.is_status() {
return Err(e.status().unwrap());
} else {
return Err(StatusCode::BAD_REQUEST);
}
}
}
// This is where de magic (and most problems) occur.
// Again, more on this later.
let content = response.unwrap().json::<T>().await;
match content {
Ok(s) => Ok(s),
Err(e) => {
println!("{:?}", e);
Err(StatusCode::BAD_REQUEST)
}
}
}
build()
function will tell which type T
corresponds to, a type that will be a struct with the Deserialize
trait so that reqwest's json()
can do the heavy lifting for us.async
calls, which required two minor tweaks:await?
, because it returns the error).let mut get_cards_request = api.cards().all_filtered(
CardFilter::builder()
.game_format(GameFormat::Standard)
.cardtypes_or(&[CardType::Instant, CardType::Sorcery])
.converted_mana_cost(2)
.rarities(&[CardRarity::Rare, CardRarity::MythicRare])
.build(),
);
let mut cards: Vec<CardDetail> = Vec::new();
loop {
let response = get_cards_request.next_page().await?
let cards = response.content;
if cards.is_empty() {
break;
}
filtered_cards.extend(cards);
}
println!("Filtered Cards: {:?}", filtered_cards);
let response = cards::filter()
.game_format("standard")
.type_field("instant|sorcery")
.cmc(2)
.rarity("rare|mythic")
.all()
.await;
println!("Filtered cards: {:?}", response.unwrap());
Option
and Iterator
, as well as crates such as warp
, implement this, giving Rust its "functional flavour".filter()
returns a struct called Where
that has a vector where I keep all the filters that are going to be added.pub struct Where<'a> {
query: Vec<(&'a str, String)>,
}
pub fn filter<'a>() -> Where<'a> {
Where { query: Vec::new() }
}
response = mtgsdk::card::filter()
, the variable response
is a Where struct
, and that allows me to call any function implemented inside Where
, e.g.:impl<'a> Where<'a> {
pub fn game_format(mut self, input: &'a str) -> Self {
self.query.push(("gameFormat", String::from(input)));
self
}
}
filter()
and then added the functions game_format()
, type_field()
, cmc()
and rarity()
I was doing this:Where
struct with filter()
game_format()
implemented inside Where
, which returned the same Where
type_field()
from the Where
returned by game_format()
cmc()
from the Where
returned by type_field()
rarity()
from the Where
returned by cmc()
all()
from the Where
returned by rarity()
which finally returned the vector of cards:
pub async fn all(mut self) -> Result<Vec<Card>, StatusCode> {
let val = self.query.remove(0);
let mut filter = format!("?{}={}", val.0, val.1);
for (k, v) in self.query.into_iter() {
filter = format!("{}&{}={}", filter, k, v);
}
let cards: Result<RootAll, StatusCode> = query_builder::filter("cards", &filter).await;
match cards {
Ok(t) => Ok(t.cards),
Err(e) => Err(e),
}
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Set {
pub code: String,
pub name: String,
#[serde(rename = "type")]
pub type_field: String,
#[serde(default)]
pub booster: Vec<Booster>,
pub release_date: String,
pub block: Option<String>,
pub online_only: Option<bool>,
pub gatherer_code: Option<String>,
pub old_code: Option<String>,
pub magic_cards_info_code: Option<String>,
pub border: Option<String>,
pub expansion: Option<String>,
pub mkm_name: Option<String>,
pub mkm_id: Option<u32>,
}
#[serde(rename_all = "camelCase")]
is not sufficiently self-explanatory, it will allow a struct field like release_date
to receive data from a field that the API calls releaseDate
;None
) sent or if it was just empty (it will me Some
)#[serde(default)]
, which I used for the mandatory fields because in these cases there's no doubt that the API sent them.Cargo.toml
I added this:[dependencies]
mtgsdk = { path = "../mtgsdk" }
tokio = { version = "1", features = ["full"] }
main.rs
I just used it as if it was a crate.use mtgsdk::cards;
#[tokio::main]
async fn main() {
let result = cards::find(46012).await;
if let Ok(card) = result{
println!("{}", card.name)
};
}