diff --git a/scryfall_deser/src/deser.rs b/scryfall_deser/src/deser.rs new file mode 100644 index 0000000..4971fa2 --- /dev/null +++ b/scryfall_deser/src/deser.rs @@ -0,0 +1,787 @@ +use chrono::NaiveDate; +use serde::Deserialize; +use serde_json::Value; +use uuid::Uuid; + +// Info from here: +// https://scryfall.com/docs/api/cards +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +#[serde(deny_unknown_fields)] +pub struct ScryfallCard { + // Core Card Fields + pub arena_id: Option, + pub id: Uuid, + pub lang: String, + pub mtgo_id: 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: Option>, + #[serde(rename = "color_indicator")] + pub colour_indicator: Option>, + #[serde(rename = "colors")] + pub colours: Option>, + pub edhrec_rank: Option, + pub defense: 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>, + pub booster: bool, + #[serde(rename = "border_color")] + pub border_colour: BorderColour, + pub card_back_id: Option, // Scryfall docs says this should not be null, but ZHS Growing Rites of Itlimoc seems to not have one... maybe it's the back side? + 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: Option, + 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>, + pub purchase_uris: Option, + pub rarity: Rarity, + pub related_uris: Value, // TODO: - list all the URIs? Maybe? Who cares? + pub released_at: NaiveDate, + pub reprint: bool, + pub scryfall_set_uri: String, // URI + pub set_name: String, + pub set_search_uri: String, // URI + pub set_type: SetType, + pub set_uri: String, // URI + pub set: String, + pub set_id: Uuid, + pub story_spotlight: bool, + pub textless: bool, + pub variation: bool, + pub variation_of: Option, + pub security_stamp: Option, + pub watermark: Option, + pub preview: Option, + + // These aren't in the Scryfall docs, but some cards do have 'em + pub foil: Option, + pub nonfoil: Option, +} + +// https://scryfall.com/docs/api/cards#card-face-objects +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +pub 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: Option>, + #[serde(rename = "color_indicator")] + pub colour_indicator: Option>, + #[serde(rename = "colors")] + pub colours: Option>, + pub defense: Option, + pub flavour_text: Option, + pub illustration_id: Option, + pub image_uris: Option, + pub layout: Option, + pub loyalty: Option, + pub mana_cost: Option, + pub name: String, + pub object: String, + pub oracle_id: Option, + pub oracle_text: Option, + pub power: Option, + pub printed_name: Option, + pub printed_text: Option, + pub printed_type_line: Option, + pub toughness: Option, + pub type_line: Option, + pub watermark: Option, +} + +// https://scryfall.com/docs/api/cards#related-card-objects +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +pub struct ScryfallRelatedCardObject { + pub id: Uuid, + pub object: String, // Always "related_card" + pub component: Component, + pub name: String, + pub type_line: String, + pub uri: String, // URI +} + +#[derive(Deserialize, PartialEq, Debug)] +pub enum Colour { + #[serde(rename = "W")] + White, + #[serde(rename = "U")] + Blue, + #[serde(rename = "B")] + Black, + #[serde(rename = "R")] + Red, + #[serde(rename = "G")] + Green, + #[serde(rename = "C")] // I don't think it's meant to work like this... but eh + Colourless, + #[serde(rename = "T")] // See "Sole Performer" + Tap, +} + +#[derive(Deserialize, Debug)] +pub 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)] +pub 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)] +pub 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)] +pub 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)] +pub enum Frame { + #[serde(rename = "1993")] + NinetyThree, + #[serde(rename = "1997")] + NinetySeven, + #[serde(rename = "2003")] + OhThree, + #[serde(rename = "2015")] + Fifteen, + #[serde(rename = "future")] + Future, +} + +// https://scryfall.com/docs/api/frames#frame-effects +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +pub 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, + #[serde(rename = "fullart")] + FullArt, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +pub enum Game { + #[serde(rename = "paper")] + Paper, + #[serde(rename = "mtgo")] + Mtgo, + #[serde(rename = "arena")] + Arena, + #[serde(rename = "astral")] + Astral, + #[serde(rename = "sega")] + Sega, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +pub 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)] +pub struct ImageURIs { + png: Option, + border_crop: Option, + art_crop: Option, + large: Option, + normal: Option, + small: Option, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +pub struct Preview { + pub previewed_at: Option, + pub source_uri: Option, // URI + pub source: Option, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +pub 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)] +pub 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, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +pub struct PurchaseUris { + tcgplayer: String, // Option? + cardmarket: String, + cardhoarder: String, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +pub enum SecurityStamp { + #[serde(rename = "oval")] + Oval, + #[serde(rename = "triangle")] + Triangle, + #[serde(rename = "acorn")] + Acorn, + #[serde(rename = "circle")] + Circle, + #[serde(rename = "arena")] + Arena, + #[serde(rename = "heart")] + Heart, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +pub enum Component { + #[serde(rename = "token")] + Token, + #[serde(rename = "meld_part")] + MeldPart, + #[serde(rename = "meld_result")] + MeldResult, + #[serde(rename = "combo_piece")] + ComboPiece, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +pub enum SetType { + #[serde(rename = "alchemy")] + Alchemy, + #[serde(rename = "archenemy")] + Archenemy, + #[serde(rename = "arsenal")] + Arsenal, + #[serde(rename = "box")] + Box, + #[serde(rename = "commander")] + Commander, + #[serde(rename = "core")] + Core, + #[serde(rename = "draft_innovation")] + DraftInnovation, + #[serde(rename = "duel_deck")] + DuelDeck, + #[serde(rename = "expansion")] + Expansion, + #[serde(rename = "from_the_vault")] + FromTheVault, + #[serde(rename = "funny")] + Funny, + #[serde(rename = "masterpiece")] + Masterpiece, + #[serde(rename = "masters")] + Masters, + #[serde(rename = "memorabilia")] + Memorabilia, + #[serde(rename = "minigame")] + Minigame, + #[serde(rename = "planechase")] + Planechase, + #[serde(rename = "premium_deck")] + PremiumDeck, + #[serde(rename = "promo")] + Promo, + #[serde(rename = "spellbook")] + SpellBook, + #[serde(rename = "starter")] + Starter, + #[serde(rename = "token")] + Token, + #[serde(rename = "treasure_chest")] + TreasureChest, + #[serde(rename = "vanguard")] + Vanguard, +} + +// TODO Complete this +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +pub enum PromoTypes { + #[serde(rename = "alchemy")] + Alchemy, + #[serde(rename = "arenaleague")] + ArenaLeague, + #[serde(rename = "beginnerbox")] + BeginnerBox, + #[serde(rename = "boosterfun")] + BoosterFun, + #[serde(rename = "boxtopper")] + BoxTopper, + #[serde(rename = "brawldeck")] + BrawlDeck, + #[serde(rename = "bundle")] + Bundle, + #[serde(rename = "buyabox")] + BuyABox, + #[serde(rename = "confettifoil")] + ConfettiFoil, + #[serde(rename = "convention")] + Convention, + #[serde(rename = "datestamped")] + DateStamped, + #[serde(rename = "dossier")] + Dossier, + #[serde(rename = "doublerainbow")] + DoubleRainbow, + #[serde(rename = "embossed")] + Embossed, + #[serde(rename = "event")] + Event, + #[serde(rename = "fnm")] + Fnm, + #[serde(rename = "gameday")] + GameDay, + #[serde(rename = "godzillaseries")] + GodzillaSeries, + #[serde(rename = "halofoil")] + HaloFoil, + #[serde(rename = "imagine")] + Imagine, + #[serde(rename = "instore")] + InStore, + #[serde(rename = "intropack")] + IntroPack, + #[serde(rename = "invisibleink")] + InvisibleInk, + #[serde(rename = "judgegift")] + JudgeGift, + #[serde(rename = "league")] + League, + #[serde(rename = "magnified")] + Magnified, + #[serde(rename = "manafoil")] + ManaFoil, + #[serde(rename = "mediainsert")] + MediaInsert, + #[serde(rename = "planeswalkerdeck")] + PlaneswalkerDeck, + #[serde(rename = "plastic")] + Plastic, + #[serde(rename = "playerrewards")] + PlayerRewards, + #[serde(rename = "playtest")] + Playtest, + #[serde(rename = "poster")] + Poster, + #[serde(rename = "prerelease")] + Prerelease, + #[serde(rename = "premiereshop")] + PremiereShop, + #[serde(rename = "promopack")] + PromoPack, + #[serde(rename = "rainbowfoil")] + RainbowFoil, + #[serde(rename = "ravnicacity")] + RavnicaCity, + #[serde(rename = "rebalanced")] + Rebalanced, + #[serde(rename = "release")] + Release, + #[serde(rename = "resale")] + Resale, + #[serde(rename = "ripplefoil")] + RippleFoil, + #[serde(rename = "setpromo")] + SetPromo, + #[serde(rename = "serialized")] + Serialised, + #[serde(rename = "silverfoil")] + SilverFoil, + #[serde(rename = "sldbonus")] + SldBonus, + #[serde(rename = "stamped")] + Stamped, + #[serde(rename = "startercollection")] + StarterCollection, + #[serde(rename = "starterdeck")] + StarterDeck, + #[serde(rename = "stepandcompleat")] + StepAndCompleat, + #[serde(rename = "surgefoil")] + SurgeFoil, + #[serde(rename = "textured")] + Textured, + #[serde(rename = "themepack")] + ThemePack, + #[serde(rename = "thick")] + Thick, + #[serde(rename = "tourney")] + Tourney, + #[serde(rename = "upsidedown")] + Upsidedown, + #[serde(rename = "vault")] + Vault, + #[serde(rename = "wizardsplaynetwork")] + WizardsPlayNetwork, +} + +#[allow(dead_code)] +pub fn weird_cards() -> Vec { + // These all seem to be double faced cards with the same "card" on both sides. + vec![ + "018830b2-dff9-45f3-9cc2-dc5b2eec0e54".to_string(), + "0489be0d-2117-46a8-97ab-31fe480685e2".to_string(), + "048ddb71-e9ea-4f11-9b8a-c53961cf3a2c".to_string(), + "087c3a0d-c710-4451-989e-596b55352184".to_string(), + "236e9bcf-ced2-4bee-8188-41dd94df02da".to_string(), + "36ea852d-ed2b-4c56-9b73-52dce8a3e520".to_string(), + "399bf36a-5901-437f-b5d3-32283cedbbcb".to_string(), + "3cb0824c-57cc-46bf-bd43-425d58b8a762".to_string(), + "fe388da5-9197-4d07-be7f-c49fcdf56dfa".to_string(), + "f973a1f3-6dcb-470d-89d2-6ddbf2426999".to_string(), + "f4e7b3a4-a346-4177-9cfe-0142b40ef4a6".to_string(), + "e25ce640-baf5-442b-8b75-d05dd9fb20dd".to_string(), + "dae8751c-4c72-4034-a192-a1e166f20246".to_string(), + "d74a72a2-d46a-41c2-a400-70571197b020".to_string(), + "d5f7a626-7b6b-41ba-a0f5-3aefe511b267".to_string(), + "d5dfd236-b1da-4552-b94f-ebf6bb9dafdf".to_string(), + "d002b29b-c3a6-4c91-86e1-96a50ce29966".to_string(), + "caf8d01d-07aa-43da-a26e-4a2ba3a76f2d".to_string(), + "c05c6c38-d204-458c-af17-4cf5efd2c7fc".to_string(), + "bffbe9ec-edbc-43ed-a3bf-60635e7e625c".to_string(), + "b96d6ea4-a3a4-4e33-be97-b3767f2bb63a".to_string(), + "acdb72e2-c000-4b92-b5ea-73115969020f".to_string(), + "aae84079-b65b-4132-86fb-e82503bb6c7b".to_string(), + "a724ebbc-0f77-42e9-95e0-b3e7cb130148".to_string(), + "a4a2dd5b-6143-4b8d-ae71-e148cf19b66c".to_string(), + "a129558c-45a1-441c-97f0-b70b4e9d8a56".to_string(), + "9f63277b-e139-46c8-b9e3-0cfb647f44cc".to_string(), + "9e69f9e0-4981-4fc0-955f-7ebe04264fca".to_string(), + "9d943cf2-0462-4f31-9a92-d76fe4971b17".to_string(), + "9cd6a16f-1eff-4624-8f7f-4d9e70a694bb".to_string(), + "9680a2d6-1d66-4f69-b400-a79fea4187d8".to_string(), + "94eea6e3-20bc-4dab-90ba-3113c120fb90".to_string(), + "94594d48-b728-4be6-9d7a-c67088df8acd".to_string(), + "3d89c9be-2489-47e4-8e53-f980c82442b4".to_string(), + "3e3f0bcd-0796-494d-bf51-94b33c1671e9".to_string(), + "4696f5de-fe5b-40df-a194-1a73b4c5150f".to_string(), + "4d227cd3-ebfe-4dd3-929a-4f8ff7c8981e".to_string(), + "5ab0412a-2b2f-430f-8830-002a42125148".to_string(), + "60c92f1b-0c78-4809-9365-e1ffa515cb4b".to_string(), + "6620b5f4-b1e5-4d1b-bbf2-c6ad9c8284c5".to_string(), + "67574bb4-c443-40fa-b7e6-05e9965c98b8".to_string(), + "6adadbc9-4a08-4c1d-adf7-edee73799d9e".to_string(), + "6c69ecd2-cb36-4628-802b-fd5ff7405f22".to_string(), + "76c343f5-6955-4ba2-a435-36d55182d1dd".to_string(), + "7e703632-5ed0-4509-a12b-594269f865f1".to_string(), + "82fa24fb-aecc-4c33-9e79-c29651ddafbe".to_string(), + "843b35ec-7b59-4a22-8fee-2e876a02306b".to_string(), + "8ae0caed-940d-45bc-9877-7cc014b2700e".to_string(), + "8b5341ab-85a6-44b2-b738-1110e699c02b".to_string(), + "8bcf942f-5afd-414e-a50d-00d884fe59da".to_string(), + "9052f5c7-ee3b-457d-97ca-ac6b4518997c".to_string(), + ] +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::io::{BufRead, BufReader}; + use std::path::PathBuf; + + #[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(); + } + + #[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(); + } + + #[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(); + } + + #[test] + fn deserialize_line_by_line_with_bad_skip() { + // This function is uuuuuuugly and I'm sure a terrible way to go about things + // It is ever so slightly faster than the other one though! + let mut f = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + f.push("test_files/all-cards.json"); + assert!(f.exists(), "You need to download the all-cards-... file from Scryfall bulk data. Can be found here: https://scryfall.com/docs/api/bulk-data and rename to all-cards.json"); + let ac = fs::File::open(f).unwrap(); + let reader = BufReader::new(ac); + let weird_cards = weird_cards(); + for line in reader.lines().skip(1) { + let mut line = line.unwrap(); + let c = line.pop().unwrap(); + // this is so dumb... + if c == '}' { + line.push('}'); + } + + // don't look... + let mut skip = false; + for weird_card in &weird_cards { + if line.contains(weird_card) { + skip = true; + break; + } + } + if skip { + continue; + } + + if line.is_empty() { + continue; + }; + + let a_card: Result = + serde_json::from_str(line.as_ref()); + if let Err(error) = a_card { + println!("{:#?}", line); + println!("{:#?}", error); + } + //let a_card = a_card.unwrap(); + //println!("{:?}", a_card.promo_types) + } + } + + #[test] + #[ignore] + fn deserialize_line_by_line() { + // This function is uuuuuuugly and I'm sure a terrible way to go about things + // It is ever so slightly faster than the other one though! + let mut f = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + f.push("test_files/all-cards.json"); + assert!(f.exists(), "You need to download the all-cards-... file from Scryfall bulk data. Can be found here: https://scryfall.com/docs/api/bulk-data and rename to all-cards.json"); + let ac = fs::File::open(f).unwrap(); + let reader = BufReader::new(ac); + for line in reader.lines().skip(1) { + let mut line = line.unwrap(); + let c = line.pop().unwrap(); + // this is so dumb... + if c == '}' { + line.push('}'); + } + + if line.is_empty() { + continue; + }; + let a_card: Result = + serde_json::from_str(line.as_ref()); + if let Err(error) = a_card { + println!("{:#?}", line); + println!("{:#?}", error); + panic!(); + } + //let a_card = a_card.unwrap(); + //println!("{:?}", a_card.promo_types) + } + } + + #[test] + #[ignore] + fn deserialize_whole_file() { + let mut f = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + f.push("test_files/all-cards.json"); + assert!(f.exists(), "You need to download the all-cards-... file from Scryfall bulk data. Can be found here: https://scryfall.com/docs/api/bulk-data and rename to all-cards.json"); + + let ac = fs::read_to_string(f).unwrap(); + let _ac: Vec = serde_json::from_str(&ac).unwrap(); + } +} diff --git a/scryfall_deser/src/download.rs b/scryfall_deser/src/download.rs new file mode 100644 index 0000000..ba8e129 --- /dev/null +++ b/scryfall_deser/src/download.rs @@ -0,0 +1,89 @@ +use serde::{Deserialize, Serialize}; +use std::io::Write; +use tempfile::NamedTempFile; +use ureq; +use uuid::Uuid; + +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +struct ScryfallBulkData { + pub id: Uuid, + pub uri: String, + #[serde(rename = "type")] + pub stype: String, + pub name: String, + pub description: String, + pub download_uri: String, + pub updated_at: String, + pub content_type: String, + pub content_encoding: String, +} + +#[allow(dead_code)] +#[derive(Deserialize, Debug)] +struct ScryfallBulk { + pub object: String, + pub has_more: bool, + pub data: Vec, +} + +#[derive(Deserialize, Serialize, PartialEq, Debug)] +pub enum ScryfallBulkType { + #[serde(rename = "oracle_cards")] + OracleCards, + #[serde(rename = "unique_artwork")] + UniqueArtwork, + #[serde(rename = "default_cards")] + DefaultCards, + #[serde(rename = "all_cards")] + AllCards, + #[serde(rename = "rulings")] + Rulings, +} + +const SCRYFALL_BULK_API: &str = "https://api.scryfall.com/bulk-data"; + +pub fn download_latest( + stype: ScryfallBulkType, + mut dest_file: &NamedTempFile, +) -> Result<(), Box> { + let bulk_body: ScryfallBulk = ureq::get(SCRYFALL_BULK_API) + .header("User-Agent", "Arthur's Card Finger Testing v0.1") + .header("Accept", "application/json") + .call()? + .body_mut() + .read_json::()?; + + let mut download_uri = String::new(); + for scryfall_bulk in bulk_body.data { + if serde_json::to_string(&stype).unwrap() == scryfall_bulk.stype { + download_uri = scryfall_bulk.download_uri; + } + } + assert!(!download_uri.is_empty()); + let cards_response = ureq::get(download_uri) + .header("User-Agent", "Arthur's Card Finger Testing v0.1") + .header("Accept", "application/json") + .call()? + .body_mut() + .with_config() + .limit(700 * 1024 * 1024) + .read_to_string()?; + write!(dest_file, "{}", cards_response)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{Seek, SeekFrom}; + #[test] + #[ignore] + fn get_scryfall_bulk_page() { + let mut file = NamedTempFile::new().unwrap(); + let _ = download_latest(ScryfallBulkType::OracleCards, &file); + let file_size = file.seek(SeekFrom::End(0)).unwrap(); + assert!(file_size > 4092); + println!("File size: {}", file_size); + } +} diff --git a/scryfall_deser/src/lib.rs b/scryfall_deser/src/lib.rs index 880b25c..f2dd881 100644 --- a/scryfall_deser/src/lib.rs +++ b/scryfall_deser/src/lib.rs @@ -1,870 +1,7 @@ -use chrono::NaiveDate; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::fs; -use std::io::{Read, Seek, SeekFrom, Write}; -use tempfile::NamedTempFile; -use ureq; -use uuid::Uuid; +mod download; -#[allow(dead_code)] -#[derive(Deserialize, Debug)] -struct ScryfallBulkData { - pub id: Uuid, - pub uri: String, - #[serde(rename = "type")] - pub stype: String, - pub name: String, - pub description: String, - pub download_uri: String, - pub updated_at: String, - pub content_type: String, - pub content_encoding: String, -} +pub use crate::download::download_latest; -#[allow(dead_code)] -#[derive(Deserialize, Debug)] -struct ScryfallBulk { - pub object: String, - pub has_more: bool, - pub data: Vec, -} +mod deser; -#[derive(Deserialize, Serialize, PartialEq, Debug)] -pub enum ScryfallBulkType { - #[serde(rename = "oracle_cards")] - OracleCards, - #[serde(rename = "unique_artwork")] - UniqueArtwork, - #[serde(rename = "default_cards")] - DefaultCards, - #[serde(rename = "all_cards")] - AllCards, - #[serde(rename = "rulings")] - Rulings, -} - -const SCRYFALL_BULK_API: &str = "https://api.scryfall.com/bulk-data"; - -pub fn download_latest( - stype: ScryfallBulkType, - mut dest_file: &NamedTempFile, -) -> Result<(), Box> { - let bulk_body: ScryfallBulk = ureq::get(SCRYFALL_BULK_API) - .header("User-Agent", "Arthur's Card Finger Testing v0.1") - .header("Accept", "application/json") - .call()? - .body_mut() - .read_json::()?; - - let mut download_uri = String::new(); - for scryfall_bulk in bulk_body.data { - if serde_json::to_string(&stype).unwrap() == scryfall_bulk.stype { - download_uri = scryfall_bulk.download_uri; - } - } - assert!(!download_uri.is_empty()); - let cards_response = ureq::get(download_uri) - .header("User-Agent", "Arthur's Card Finger Testing v0.1") - .header("Accept", "application/json") - .call()? - .body_mut() - .with_config() - .limit(700 * 1024 * 1024) - .read_to_string()?; - write!(dest_file, "{}", cards_response)?; - Ok(()) -} - -// Info from here: -// https://scryfall.com/docs/api/cards -#[allow(dead_code)] -#[derive(Deserialize, Debug)] -#[serde(deny_unknown_fields)] -struct ScryfallCard { - // Core Card Fields - pub arena_id: Option, - pub id: Uuid, - pub lang: String, - pub mtgo_id: 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: Option>, - #[serde(rename = "color_indicator")] - pub colour_indicator: Option>, - #[serde(rename = "colors")] - pub colours: Option>, - pub edhrec_rank: Option, - pub defense: 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>, - pub booster: bool, - #[serde(rename = "border_color")] - pub border_colour: BorderColour, - pub card_back_id: Option, // Scryfall docs says this should not be null, but ZHS Growing Rites of Itlimoc seems to not have one... maybe it's the back side? - 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: Option, - 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>, - pub purchase_uris: Option, - pub rarity: Rarity, - pub related_uris: Value, // TODO: - list all the URIs? Maybe? Who cares? - pub released_at: NaiveDate, - pub reprint: bool, - pub scryfall_set_uri: String, // URI - pub set_name: String, - pub set_search_uri: String, // URI - pub set_type: SetType, - pub set_uri: String, // URI - pub set: String, - pub set_id: Uuid, - pub story_spotlight: bool, - pub textless: bool, - pub variation: bool, - pub variation_of: Option, - pub security_stamp: Option, - pub watermark: Option, - pub preview: Option, - - // These aren't in the Scryfall docs, but some cards do have 'em - pub foil: Option, - pub nonfoil: Option, -} - -// 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: Option>, - #[serde(rename = "color_indicator")] - pub colour_indicator: Option>, - #[serde(rename = "colors")] - pub colours: Option>, - pub defense: Option, - pub flavour_text: Option, - pub illustration_id: Option, - pub image_uris: Option, - pub layout: Option, - pub loyalty: Option, - pub mana_cost: Option, - pub name: String, - pub object: String, - pub oracle_id: Option, - pub oracle_text: Option, - pub power: Option, - pub printed_name: Option, - pub printed_text: Option, - pub printed_type_line: Option, - pub toughness: Option, - pub type_line: Option, - pub watermark: Option, -} - -// 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: Component, - 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, - #[serde(rename = "C")] // I don't think it's meant to work like this... but eh - Colourless, - #[serde(rename = "T")] // See "Sole Performer" - Tap, -} - -#[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")] - Fifteen, - #[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, - #[serde(rename = "fullart")] - FullArt, -} - -#[allow(dead_code)] -#[derive(Deserialize, Debug)] -enum Game { - #[serde(rename = "paper")] - Paper, - #[serde(rename = "mtgo")] - Mtgo, - #[serde(rename = "arena")] - Arena, - #[serde(rename = "astral")] - Astral, - #[serde(rename = "sega")] - Sega, -} - -#[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 Preview { - pub previewed_at: Option, - pub source_uri: Option, // URI - pub source: 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, -} - -#[allow(dead_code)] -#[derive(Deserialize, Debug)] -struct PurchaseUris { - tcgplayer: String, // Option? - cardmarket: String, - cardhoarder: String, -} - -#[allow(dead_code)] -#[derive(Deserialize, Debug)] -enum SecurityStamp { - #[serde(rename = "oval")] - Oval, - #[serde(rename = "triangle")] - Triangle, - #[serde(rename = "acorn")] - Acorn, - #[serde(rename = "circle")] - Circle, - #[serde(rename = "arena")] - Arena, - #[serde(rename = "heart")] - Heart, -} - -#[allow(dead_code)] -#[derive(Deserialize, Debug)] -enum Component { - #[serde(rename = "token")] - Token, - #[serde(rename = "meld_part")] - MeldPart, - #[serde(rename = "meld_result")] - MeldResult, - #[serde(rename = "combo_piece")] - ComboPiece, -} - -#[allow(dead_code)] -#[derive(Deserialize, Debug)] -enum SetType { - #[serde(rename = "alchemy")] - Alchemy, - #[serde(rename = "archenemy")] - Archenemy, - #[serde(rename = "arsenal")] - Arsenal, - #[serde(rename = "box")] - Box, - #[serde(rename = "commander")] - Commander, - #[serde(rename = "core")] - Core, - #[serde(rename = "draft_innovation")] - DraftInnovation, - #[serde(rename = "duel_deck")] - DuelDeck, - #[serde(rename = "expansion")] - Expansion, - #[serde(rename = "from_the_vault")] - FromTheVault, - #[serde(rename = "funny")] - Funny, - #[serde(rename = "masterpiece")] - Masterpiece, - #[serde(rename = "masters")] - Masters, - #[serde(rename = "memorabilia")] - Memorabilia, - #[serde(rename = "minigame")] - Minigame, - #[serde(rename = "planechase")] - Planechase, - #[serde(rename = "premium_deck")] - PremiumDeck, - #[serde(rename = "promo")] - Promo, - #[serde(rename = "spellbook")] - SpellBook, - #[serde(rename = "starter")] - Starter, - #[serde(rename = "token")] - Token, - #[serde(rename = "treasure_chest")] - TreasureChest, - #[serde(rename = "vanguard")] - Vanguard, -} - -// TODO Complete this -#[allow(dead_code)] -#[derive(Deserialize, Debug)] -enum PromoTypes { - #[serde(rename = "alchemy")] - Alchemy, - #[serde(rename = "arenaleague")] - ArenaLeague, - #[serde(rename = "beginnerbox")] - BeginnerBox, - #[serde(rename = "boosterfun")] - BoosterFun, - #[serde(rename = "boxtopper")] - BoxTopper, - #[serde(rename = "brawldeck")] - BrawlDeck, - #[serde(rename = "bundle")] - Bundle, - #[serde(rename = "buyabox")] - BuyABox, - #[serde(rename = "confettifoil")] - ConfettiFoil, - #[serde(rename = "convention")] - Convention, - #[serde(rename = "datestamped")] - DateStamped, - #[serde(rename = "dossier")] - Dossier, - #[serde(rename = "doublerainbow")] - DoubleRainbow, - #[serde(rename = "embossed")] - Embossed, - #[serde(rename = "event")] - Event, - #[serde(rename = "fnm")] - Fnm, - #[serde(rename = "gameday")] - GameDay, - #[serde(rename = "godzillaseries")] - GodzillaSeries, - #[serde(rename = "halofoil")] - HaloFoil, - #[serde(rename = "imagine")] - Imagine, - #[serde(rename = "instore")] - InStore, - #[serde(rename = "intropack")] - IntroPack, - #[serde(rename = "invisibleink")] - InvisibleInk, - #[serde(rename = "judgegift")] - JudgeGift, - #[serde(rename = "league")] - League, - #[serde(rename = "magnified")] - Magnified, - #[serde(rename = "manafoil")] - ManaFoil, - #[serde(rename = "mediainsert")] - MediaInsert, - #[serde(rename = "planeswalkerdeck")] - PlaneswalkerDeck, - #[serde(rename = "plastic")] - Plastic, - #[serde(rename = "playerrewards")] - PlayerRewards, - #[serde(rename = "playtest")] - Playtest, - #[serde(rename = "poster")] - Poster, - #[serde(rename = "prerelease")] - Prerelease, - #[serde(rename = "premiereshop")] - PremiereShop, - #[serde(rename = "promopack")] - PromoPack, - #[serde(rename = "rainbowfoil")] - RainbowFoil, - #[serde(rename = "ravnicacity")] - RavnicaCity, - #[serde(rename = "rebalanced")] - Rebalanced, - #[serde(rename = "release")] - Release, - #[serde(rename = "resale")] - Resale, - #[serde(rename = "ripplefoil")] - RippleFoil, - #[serde(rename = "setpromo")] - SetPromo, - #[serde(rename = "serialized")] - Serialised, - #[serde(rename = "silverfoil")] - SilverFoil, - #[serde(rename = "sldbonus")] - SldBonus, - #[serde(rename = "stamped")] - Stamped, - #[serde(rename = "startercollection")] - StarterCollection, - #[serde(rename = "starterdeck")] - StarterDeck, - #[serde(rename = "stepandcompleat")] - StepAndCompleat, - #[serde(rename = "surgefoil")] - SurgeFoil, - #[serde(rename = "textured")] - Textured, - #[serde(rename = "themepack")] - ThemePack, - #[serde(rename = "thick")] - Thick, - #[serde(rename = "tourney")] - Tourney, - #[serde(rename = "upsidedown")] - Upsidedown, - #[serde(rename = "vault")] - Vault, - #[serde(rename = "wizardsplaynetwork")] - WizardsPlayNetwork, -} - -#[allow(dead_code)] -fn weird_cards() -> Vec { - // These all seem to be double faced cards with the same "card" on both sides. - vec![ - "018830b2-dff9-45f3-9cc2-dc5b2eec0e54".to_string(), - "0489be0d-2117-46a8-97ab-31fe480685e2".to_string(), - "048ddb71-e9ea-4f11-9b8a-c53961cf3a2c".to_string(), - "087c3a0d-c710-4451-989e-596b55352184".to_string(), - "236e9bcf-ced2-4bee-8188-41dd94df02da".to_string(), - "36ea852d-ed2b-4c56-9b73-52dce8a3e520".to_string(), - "399bf36a-5901-437f-b5d3-32283cedbbcb".to_string(), - "3cb0824c-57cc-46bf-bd43-425d58b8a762".to_string(), - "fe388da5-9197-4d07-be7f-c49fcdf56dfa".to_string(), - "f973a1f3-6dcb-470d-89d2-6ddbf2426999".to_string(), - "f4e7b3a4-a346-4177-9cfe-0142b40ef4a6".to_string(), - "e25ce640-baf5-442b-8b75-d05dd9fb20dd".to_string(), - "dae8751c-4c72-4034-a192-a1e166f20246".to_string(), - "d74a72a2-d46a-41c2-a400-70571197b020".to_string(), - "d5f7a626-7b6b-41ba-a0f5-3aefe511b267".to_string(), - "d5dfd236-b1da-4552-b94f-ebf6bb9dafdf".to_string(), - "d002b29b-c3a6-4c91-86e1-96a50ce29966".to_string(), - "caf8d01d-07aa-43da-a26e-4a2ba3a76f2d".to_string(), - "c05c6c38-d204-458c-af17-4cf5efd2c7fc".to_string(), - "bffbe9ec-edbc-43ed-a3bf-60635e7e625c".to_string(), - "b96d6ea4-a3a4-4e33-be97-b3767f2bb63a".to_string(), - "acdb72e2-c000-4b92-b5ea-73115969020f".to_string(), - "aae84079-b65b-4132-86fb-e82503bb6c7b".to_string(), - "a724ebbc-0f77-42e9-95e0-b3e7cb130148".to_string(), - "a4a2dd5b-6143-4b8d-ae71-e148cf19b66c".to_string(), - "a129558c-45a1-441c-97f0-b70b4e9d8a56".to_string(), - "9f63277b-e139-46c8-b9e3-0cfb647f44cc".to_string(), - "9e69f9e0-4981-4fc0-955f-7ebe04264fca".to_string(), - "9d943cf2-0462-4f31-9a92-d76fe4971b17".to_string(), - "9cd6a16f-1eff-4624-8f7f-4d9e70a694bb".to_string(), - "9680a2d6-1d66-4f69-b400-a79fea4187d8".to_string(), - "94eea6e3-20bc-4dab-90ba-3113c120fb90".to_string(), - "94594d48-b728-4be6-9d7a-c67088df8acd".to_string(), - "3d89c9be-2489-47e4-8e53-f980c82442b4".to_string(), - "3e3f0bcd-0796-494d-bf51-94b33c1671e9".to_string(), - "4696f5de-fe5b-40df-a194-1a73b4c5150f".to_string(), - "4d227cd3-ebfe-4dd3-929a-4f8ff7c8981e".to_string(), - "5ab0412a-2b2f-430f-8830-002a42125148".to_string(), - "60c92f1b-0c78-4809-9365-e1ffa515cb4b".to_string(), - "6620b5f4-b1e5-4d1b-bbf2-c6ad9c8284c5".to_string(), - "67574bb4-c443-40fa-b7e6-05e9965c98b8".to_string(), - "6adadbc9-4a08-4c1d-adf7-edee73799d9e".to_string(), - "6c69ecd2-cb36-4628-802b-fd5ff7405f22".to_string(), - "76c343f5-6955-4ba2-a435-36d55182d1dd".to_string(), - "7e703632-5ed0-4509-a12b-594269f865f1".to_string(), - "82fa24fb-aecc-4c33-9e79-c29651ddafbe".to_string(), - "843b35ec-7b59-4a22-8fee-2e876a02306b".to_string(), - "8ae0caed-940d-45bc-9877-7cc014b2700e".to_string(), - "8b5341ab-85a6-44b2-b738-1110e699c02b".to_string(), - "8bcf942f-5afd-414e-a50d-00d884fe59da".to_string(), - "9052f5c7-ee3b-457d-97ca-ac6b4518997c".to_string(), - ] -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use std::io::{BufRead, BufReader}; - use std::path::PathBuf; - - #[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(); - } - - #[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(); - } - - #[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(); - } - - #[test] - fn deserialize_line_by_line_with_bad_skip() { - // This function is uuuuuuugly and I'm sure a terrible way to go about things - // It is ever so slightly faster than the other one though! - let mut f = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - f.push("test_files/all-cards.json"); - assert!(f.exists(), "You need to download the all-cards-... file from Scryfall bulk data. Can be found here: https://scryfall.com/docs/api/bulk-data and rename to all-cards.json"); - let ac = fs::File::open(f).unwrap(); - let reader = BufReader::new(ac); - let weird_cards = weird_cards(); - for line in reader.lines().skip(1) { - let mut line = line.unwrap(); - let c = line.pop().unwrap(); - // this is so dumb... - if c == '}' { - line.push('}'); - } - - // don't look... - let mut skip = false; - for weird_card in &weird_cards { - if line.contains(weird_card) { - skip = true; - break; - } - } - if skip { - continue; - } - - if line.is_empty() { - continue; - }; - - let a_card: Result = - serde_json::from_str(line.as_ref()); - if let Err(error) = a_card { - println!("{:#?}", line); - println!("{:#?}", error); - } - //let a_card = a_card.unwrap(); - //println!("{:?}", a_card.promo_types) - } - } - - #[test] - #[ignore] - fn deserialize_line_by_line() { - // This function is uuuuuuugly and I'm sure a terrible way to go about things - // It is ever so slightly faster than the other one though! - let mut f = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - f.push("test_files/all-cards.json"); - assert!(f.exists(), "You need to download the all-cards-... file from Scryfall bulk data. Can be found here: https://scryfall.com/docs/api/bulk-data and rename to all-cards.json"); - let ac = fs::File::open(f).unwrap(); - let reader = BufReader::new(ac); - for line in reader.lines().skip(1) { - let mut line = line.unwrap(); - let c = line.pop().unwrap(); - // this is so dumb... - if c == '}' { - line.push('}'); - } - - if line.is_empty() { - continue; - }; - let a_card: Result = - serde_json::from_str(line.as_ref()); - if let Err(error) = a_card { - println!("{:#?}", line); - println!("{:#?}", error); - panic!(); - } - //let a_card = a_card.unwrap(); - //println!("{:?}", a_card.promo_types) - } - } - - #[test] - #[ignore] - fn deserialize_whole_file() { - let mut f = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - f.push("test_files/all-cards.json"); - assert!(f.exists(), "You need to download the all-cards-... file from Scryfall bulk data. Can be found here: https://scryfall.com/docs/api/bulk-data and rename to all-cards.json"); - - let ac = fs::read_to_string(f).unwrap(); - let _ac: Vec = serde_json::from_str(&ac).unwrap(); - } - - #[test] - #[ignore] - fn get_scryfall_bulk_page() { - let mut file = NamedTempFile::new().unwrap(); - let _ = download_latest(ScryfallBulkType::OracleCards, &file); - let file_size = file.seek(SeekFrom::End(0)).unwrap(); - assert!(file_size > 4092); - println!("File size: {}", file_size); - } -} +pub use crate::deser::ScryfallCard;