And fixed up the fact the promo types are optionally null/not there/empty/full
390 lines
11 KiB
Rust
390 lines
11 KiB
Rust
use serde::Deserialize;
|
|
use uuid::Uuid;
|
|
|
|
// Info from here:
|
|
// https://scryfall.com/docs/api/cards
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
struct ScryfallCard {
|
|
// Core Card Fields
|
|
pub arena_id: Option<u64>,
|
|
pub id: Uuid,
|
|
pub lang: String,
|
|
pub mtgo: Option<u64>,
|
|
pub mtgo_foil_id: Option<u64>,
|
|
pub multiverse_ids: Option<Vec<u64>>,
|
|
pub tcgplayer_id: Option<u64>,
|
|
pub tcgplayer_etched_id: Option<u64>,
|
|
pub cardmarket_id: Option<u64>,
|
|
pub object: String,
|
|
pub layout: String, // Perhaps some kind of enum of these: https://scryfall.com/docs/api/layouts?
|
|
pub oracle_id: Option<Uuid>,
|
|
pub prints_search_uri: String, // URI
|
|
pub rulings_uri: String, // URI
|
|
pub scryfall_uri: String, // URI
|
|
pub uri: String, //URI
|
|
|
|
// Gameplay Fields
|
|
// https://scryfall.com/docs/api/cards#gameplay-fields
|
|
pub all_parts: Option<Vec<ScryfallRelatedCardObject>>,
|
|
pub card_faces: Option<Vec<ScryfallCardFaceObject>>,
|
|
|
|
// NOTE: Much of the next is a repeat of what's in the ScryfallCardFaceObject if you change something here, change something there
|
|
// NOTE: Probably a bad idea to rename color -> colour just for the sake
|
|
pub cmc: Option<f64>, // TODO: Make this a proper Decimal - see "Little Girl" card for example of cmc of 0.5
|
|
#[serde(rename = "color_identity")]
|
|
pub colour_identity: Vec<Colour>,
|
|
#[serde(rename = "color_indicator")]
|
|
pub colour_indicator: Option<Vec<Colour>>,
|
|
#[serde(rename = "colors")]
|
|
pub colours: Option<Vec<Colour>>,
|
|
pub edhrec_rank: Option<u64>,
|
|
pub defence: Option<String>,
|
|
pub hand_modifier: Option<String>,
|
|
pub keywords: Vec<String>, // Words like "Flying"
|
|
pub legalities: FormatLegalities,
|
|
pub life_modifier: Option<String>,
|
|
pub loyalty: Option<String>,
|
|
pub mana_cost: Option<String>,
|
|
pub name: String,
|
|
pub oracle_text: Option<String>,
|
|
pub penny_rank: Option<u64>,
|
|
pub power: Option<String>,
|
|
pub produced_mana: Option<Vec<Colour>>,
|
|
pub reserved: bool,
|
|
pub toughness: Option<String>,
|
|
pub type_line: String,
|
|
|
|
// Print Fields
|
|
// https://scryfall.com/docs/api/cards#print-fields
|
|
pub artist: Option<String>,
|
|
pub artist_ids: Option<Vec<String>>,
|
|
pub attraction_lights: Option<Vec<String>>, // TODO: I'm not actually sure what these look like - I should test
|
|
pub booster: bool,
|
|
#[serde(rename = "border_color")]
|
|
pub border_colour: BorderColour,
|
|
pub card_back_id: Uuid,
|
|
pub collector_number: String,
|
|
pub content_warning: Option<bool>,
|
|
pub digital: bool,
|
|
pub finishes: Vec<Finish>,
|
|
#[serde(rename = "flavor_name")]
|
|
pub flavour_name: Option<String>,
|
|
#[serde(rename = "flavor_text")]
|
|
pub flavour_text: Option<String>,
|
|
pub frame_effects: Option<Vec<FrameEffect>>,
|
|
pub frame: Frame,
|
|
pub full_art: bool,
|
|
pub games: Vec<Game>,
|
|
pub highres_image: bool,
|
|
pub illustration_id: Option<Uuid>,
|
|
pub image_status: ImageStatus,
|
|
pub image_uris: ImageURIs,
|
|
pub oversized: bool,
|
|
pub prices: Prices,
|
|
pub printed_name: Option<String>,
|
|
pub printed_text: Option<String>,
|
|
pub printed_type_line: Option<String>,
|
|
pub promo: bool,
|
|
pub promo_types: Option<Vec<String>>, // TODO: Check what types exist - could be a enumeratable selection
|
|
//pub purchase_uris: Option<Vec<bool>>, // FIXME
|
|
pub rarity: Rarity,
|
|
// TODO - the rest (and the purachase URIs above)
|
|
}
|
|
|
|
// https://scryfall.com/docs/api/cards#card-face-objects
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
struct ScryfallCardFaceObject {
|
|
pub artist: Option<String>,
|
|
pub artist_id: Option<Uuid>, // UUID
|
|
pub cmc: Option<f64>, // TODO: Make this a proper Decimal - see "Little Girl" card for example of cmc of 0.5
|
|
#[serde(rename = "color_identity")]
|
|
pub colour_identity: Vec<Colour>,
|
|
#[serde(rename = "color_indicator")]
|
|
pub colour_indicator: Option<Vec<Colour>>,
|
|
#[serde(rename = "colors")]
|
|
pub colours: Option<Vec<Colour>>,
|
|
pub defence: Option<String>,
|
|
// TODO: Complete
|
|
}
|
|
|
|
// https://scryfall.com/docs/api/cards#related-card-objects
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
struct ScryfallRelatedCardObject {
|
|
pub id: Uuid,
|
|
pub object: String, // Always "related_card"
|
|
pub component: String, // One of token, meld_part, meld_result, combo_piece
|
|
pub name: String,
|
|
pub type_line: String,
|
|
pub uri: String // URI
|
|
}
|
|
|
|
#[derive(Deserialize, PartialEq, Debug)]
|
|
enum Colour {
|
|
#[serde(rename = "W")]
|
|
White,
|
|
#[serde(rename = "U")]
|
|
Blue,
|
|
#[serde(rename = "B")]
|
|
Black,
|
|
#[serde(rename = "R")]
|
|
Red,
|
|
#[serde(rename = "G")]
|
|
Green,
|
|
}
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
enum Legality {
|
|
#[serde(rename = "legal")]
|
|
Legal,
|
|
#[serde(rename = "not_legal")]
|
|
NotLegal,
|
|
#[serde(rename = "banned")]
|
|
Banned,
|
|
#[serde(rename = "restricted")]
|
|
Restricted,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
struct FormatLegalities {
|
|
standard: Legality,
|
|
future: Legality,
|
|
historic: Legality,
|
|
timeless: Legality,
|
|
gladiator: Legality,
|
|
pioneer: Legality,
|
|
explorer: Legality,
|
|
modern: Legality,
|
|
legacy: Legality,
|
|
pauper: Legality,
|
|
vintage: Legality,
|
|
penny: Legality,
|
|
commander: Legality,
|
|
oathbreaker: Legality,
|
|
standardbrawl: Legality,
|
|
brawl: Legality,
|
|
alchemy: Legality,
|
|
paupercommander: Legality,
|
|
duel: Legality,
|
|
oldschool: Legality,
|
|
premodern: Legality,
|
|
predh: Legality
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
enum BorderColour {
|
|
#[serde(rename = "black")]
|
|
Black,
|
|
#[serde(rename = "white")]
|
|
White,
|
|
#[serde(rename = "borderless")]
|
|
Borderless,
|
|
#[serde(rename = "yellow")]
|
|
Yellow,
|
|
#[serde(rename = "silver")]
|
|
Silver,
|
|
#[serde(rename = "gold")]
|
|
Gold
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
enum Finish {
|
|
#[serde(rename = "foil")]
|
|
Foil,
|
|
#[serde(rename = "nonfoil")]
|
|
NonFoil,
|
|
#[serde(rename = "etched")]
|
|
Etched
|
|
}
|
|
|
|
// https://scryfall.com/docs/api/frames#frames
|
|
// This is probably dumb...
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
enum Frame {
|
|
#[serde(rename = "1993")]
|
|
NinetyThree,
|
|
#[serde(rename = "1997")]
|
|
NinetySeven,
|
|
#[serde(rename = "2003")]
|
|
OhThree,
|
|
#[serde(rename = "2015")]
|
|
OhFifteen,
|
|
#[serde(rename = "future")]
|
|
Future
|
|
}
|
|
|
|
// https://scryfall.com/docs/api/frames#frame-effects
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
enum FrameEffect {
|
|
#[serde(rename = "legendary")]
|
|
Legendary,
|
|
#[serde(rename = "miracle")]
|
|
Miracle,
|
|
#[serde(rename = "enchantment")]
|
|
Enchantment,
|
|
#[serde(rename = "draft")]
|
|
Draft,
|
|
#[serde(rename = "devoid")]
|
|
Devoid,
|
|
#[serde(rename = "tombstone")]
|
|
Tombstone,
|
|
#[serde(rename = "colorshifted")]
|
|
Colourshifted,
|
|
#[serde(rename = "inverted")]
|
|
Inverted,
|
|
#[serde(rename = "sunmoondfc")]
|
|
SunMoonDFC,
|
|
#[serde(rename = "compasslanddfc")]
|
|
CompassLandDFC,
|
|
#[serde(rename = "originpwdfc")]
|
|
OriginPwDFC,
|
|
#[serde(rename = "mooneldrazidfc")]
|
|
MoonEldraziDFC,
|
|
#[serde(rename = "waxingandwaningmoondfc")]
|
|
WaxingAndWaningMoonDFC,
|
|
#[serde(rename = "showcase")]
|
|
Showcase,
|
|
#[serde(rename = "extendedart")]
|
|
ExtendedArt,
|
|
#[serde(rename = "companion")]
|
|
Companion,
|
|
#[serde(rename = "etched")]
|
|
Etched,
|
|
#[serde(rename = "snow")]
|
|
Snow,
|
|
#[serde(rename = "lesson")]
|
|
Lesson,
|
|
#[serde(rename = "shatteredglass")]
|
|
ShatteredGlass,
|
|
#[serde(rename = "convertdfc")]
|
|
ConvertDFC,
|
|
#[serde(rename = "fandfc")]
|
|
FanDFC,
|
|
#[serde(rename = "upsidedowndfc")]
|
|
UpsideDownDFC,
|
|
#[serde(rename = "spree")]
|
|
Spree
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
enum Game {
|
|
#[serde(rename = "paper")]
|
|
Paper,
|
|
#[serde(rename = "mtgo")]
|
|
MTGO,
|
|
#[serde(rename = "arena")]
|
|
Arena
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
enum ImageStatus {
|
|
#[serde(rename = "missing")]
|
|
Missing,
|
|
#[serde(rename = "placeholder")]
|
|
Placeholder,
|
|
#[serde(rename = "lowres")]
|
|
LowResolution,
|
|
#[serde(rename = "highres_scan")]
|
|
HighResolutionScan
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
struct ImageURIs {
|
|
png: Option<String>,
|
|
border_crop: Option<String>,
|
|
art_crop: Option<String>,
|
|
large: Option<String>,
|
|
normal: Option<String>,
|
|
small: Option<String>
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
struct Prices {
|
|
usd: Option<String>, // TODO Convert to f64?
|
|
usd_foil: Option<String>,
|
|
usd_etched: Option<String>,
|
|
eur: Option<String>,
|
|
eur_foil: Option<String>,
|
|
tix: Option<String>
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[derive(Deserialize, Debug)]
|
|
enum Rarity {
|
|
#[serde(rename = "common")]
|
|
Common,
|
|
#[serde(rename = "uncommon")]
|
|
Uncommon,
|
|
#[serde(rename = "rare")]
|
|
Rare,
|
|
#[serde(rename = "special")]
|
|
Special,
|
|
#[serde(rename = "mythic")]
|
|
Mythic,
|
|
#[serde(rename = "bonus")]
|
|
Bonus
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::path::PathBuf;
|
|
use std::fs;
|
|
|
|
#[test]
|
|
fn deserialise_nissa() {
|
|
let mut f = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
f.push("test_files/nissa.json");
|
|
assert!(f.exists());
|
|
let fc = fs::read_to_string(f).unwrap();
|
|
let nissa: ScryfallCard = serde_json::from_str(&fc).unwrap();
|
|
println!("{:#?}", nissa);
|
|
}
|
|
|
|
#[test]
|
|
fn deserialise_black_lotus() {
|
|
let mut f = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
f.push("test_files/black_lotus.json");
|
|
assert!(f.exists());
|
|
let fc = fs::read_to_string(f).unwrap();
|
|
let bl: ScryfallCard = serde_json::from_str(&fc).unwrap();
|
|
println!("{:#?}", bl);
|
|
}
|
|
|
|
#[test]
|
|
fn deserialise_little_girl() {
|
|
let mut f = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
f.push("test_files/little_girl.json");
|
|
assert!(f.exists());
|
|
let fc = fs::read_to_string(f).unwrap();
|
|
let lg: ScryfallCard = serde_json::from_str(&fc).unwrap();
|
|
println!("{:#?}", lg);
|
|
}
|
|
|
|
#[test]
|
|
#[ignore]
|
|
fn try_deserialise_all_cards() {
|
|
// TODO Actually write this funtion.
|
|
// The idea will be to run over one of the Scryfall dumps of all the cards so I can be
|
|
// confident I've got things working.
|
|
// I noticed that Scryfall very helpfully provides their dumps with 1 card per line.
|
|
// So using something like the link below should hopefully mean I don't have to load in the
|
|
// whole 2+GB file all at once
|
|
// https://doc.rust-lang.org/std/io/trait.BufRead.html#method.lines
|
|
// I think that's what it should do at least. I bet it'll still take a while tho.
|
|
// If that doesn't work - I should try somehow use the serde from_reader function
|
|
// I don't 100% know how I'll use that though.
|
|
}
|
|
}
|