Files
mini_projects/scryfall_deser/src/lib.rs
Arthur Roberts f576fe6cc8 Added UUID
And fixed up the fact the promo types are optionally null/not there/empty/full
2025-02-04 22:40:51 +00:00

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.
}
}