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, pub id: Uuid, pub lang: String, pub mtgo: Option, pub mtgo_foil_id: Option, pub multiverse_ids: Option>, pub tcgplayer_id: Option, pub tcgplayer_etched_id: Option, pub cardmarket_id: Option, pub object: String, pub layout: String, // Perhaps some kind of enum of these: https://scryfall.com/docs/api/layouts? pub oracle_id: Option, 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>, pub card_faces: Option>, // 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, // 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, #[serde(rename = "color_indicator")] pub colour_indicator: Option>, #[serde(rename = "colors")] pub colours: Option>, pub edhrec_rank: Option, pub defence: Option, pub hand_modifier: Option, pub keywords: Vec, // Words like "Flying" pub legalities: FormatLegalities, pub life_modifier: Option, pub loyalty: Option, pub mana_cost: Option, pub name: String, pub oracle_text: Option, pub penny_rank: Option, pub power: Option, pub produced_mana: Option>, pub reserved: bool, pub toughness: Option, pub type_line: String, // Print Fields // https://scryfall.com/docs/api/cards#print-fields pub artist: Option, pub artist_ids: Option>, pub attraction_lights: Option>, // 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, pub digital: bool, pub finishes: Vec, #[serde(rename = "flavor_name")] pub flavour_name: Option, #[serde(rename = "flavor_text")] pub flavour_text: Option, pub frame_effects: Option>, pub frame: Frame, pub full_art: bool, pub games: Vec, pub highres_image: bool, pub illustration_id: Option, pub image_status: ImageStatus, pub image_uris: ImageURIs, pub oversized: bool, pub prices: Prices, pub printed_name: Option, pub printed_text: Option, pub printed_type_line: Option, pub promo: bool, pub promo_types: Option>, // TODO: Check what types exist - could be a enumeratable selection //pub purchase_uris: Option>, // 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, pub artist_id: Option, // UUID pub cmc: Option, // 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, #[serde(rename = "color_indicator")] pub colour_indicator: Option>, #[serde(rename = "colors")] pub colours: Option>, pub defence: Option, // 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, border_crop: Option, art_crop: Option, large: Option, normal: Option, small: Option } #[allow(dead_code)] #[derive(Deserialize, Debug)] struct Prices { usd: Option, // TODO Convert to f64? usd_foil: Option, usd_etched: Option, eur: Option, eur_foil: Option, tix: Option } #[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. } }