Compare commits

...

3 Commits

Author SHA1 Message Date
a14d2dcb75 Don't look at that awful test...
I have tested it on the Oracle cards - so should like... mostly work for cards

Except for, I think, mistakes? in the Scryfall database.
2025-02-05 00:13:48 +00:00
66b22c1c30 Added a very poorly written test thingy
Started fixing a couple of mistakes
2025-02-04 23:45:53 +00:00
993ac46954 Got most of the fields I think 2025-02-04 23:21:23 +00:00
3 changed files with 134 additions and 30 deletions

View File

@@ -2,6 +2,7 @@
# will have compiled files and executables # will have compiled files and executables
debug/ debug/
target/ target/
test_files/all-cards.json
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html

View File

@@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
chrono = { version = "0.4.39", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.138" serde_json = "1.0.138"
uuid = { version = "1.12.1", features = ["v4", "serde"] } uuid = { version = "1.12.1", features = ["v4", "serde"] }

View File

@@ -1,5 +1,7 @@
use serde::Deserialize; use serde::Deserialize;
use serde_json::Value;
use uuid::Uuid; use uuid::Uuid;
use chrono::NaiveDate;
// Info from here: // Info from here:
// https://scryfall.com/docs/api/cards // https://scryfall.com/docs/api/cards
@@ -33,7 +35,7 @@ struct ScryfallCard {
// NOTE: Probably a bad idea to rename color -> colour just for the sake // 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 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")] #[serde(rename = "color_identity")]
pub colour_identity: Vec<Colour>, pub colour_identity: Option<Vec<Colour>>,
#[serde(rename = "color_indicator")] #[serde(rename = "color_indicator")]
pub colour_indicator: Option<Vec<Colour>>, pub colour_indicator: Option<Vec<Colour>>,
#[serde(rename = "colors")] #[serde(rename = "colors")]
@@ -59,11 +61,11 @@ struct ScryfallCard {
// https://scryfall.com/docs/api/cards#print-fields // https://scryfall.com/docs/api/cards#print-fields
pub artist: Option<String>, pub artist: Option<String>,
pub artist_ids: Option<Vec<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 attraction_lights: Option<Vec<u8>>,
pub booster: bool, pub booster: bool,
#[serde(rename = "border_color")] #[serde(rename = "border_color")]
pub border_colour: BorderColour, pub border_colour: BorderColour,
pub card_back_id: Uuid, pub card_back_id: Option<Uuid>, // 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 collector_number: String,
pub content_warning: Option<bool>, pub content_warning: Option<bool>,
pub digital: bool, pub digital: bool,
@@ -79,17 +81,39 @@ struct ScryfallCard {
pub highres_image: bool, pub highres_image: bool,
pub illustration_id: Option<Uuid>, pub illustration_id: Option<Uuid>,
pub image_status: ImageStatus, pub image_status: ImageStatus,
pub image_uris: ImageURIs, pub image_uris: Option<ImageURIs>,
pub oversized: bool, pub oversized: bool,
pub prices: Prices, pub prices: Prices,
pub printed_name: Option<String>, pub printed_name: Option<String>,
pub printed_text: Option<String>, pub printed_text: Option<String>,
pub printed_type_line: Option<String>, pub printed_type_line: Option<String>,
pub promo: bool, pub promo: bool,
pub promo_types: Option<Vec<String>>, // TODO: Check what types exist - could be a enumeratable selection pub promo_types: Option<Vec<String>>, // TODO enum?
//pub purchase_uris: Option<Vec<bool>>, // FIXME pub purchase_uris: Option<PurchaseUris>,
pub rarity: Rarity, pub rarity: Rarity,
// TODO - the rest (and the purachase URIs above) 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: String, // TODO: Enum?
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<Uuid>,
pub security_stamp: Option<SecurityStamp>,
pub watermark: Option<String>,
#[serde(rename = "preview.previewed_at")]
pub preview_previewed_at: Option<NaiveDate>,
#[serde(rename = "preview.source_uri")]
pub preview_source_uri: Option<String>, // URI
#[serde(rename = "preview.source")]
pub preview_source: Option<String>
} }
// https://scryfall.com/docs/api/cards#card-face-objects // https://scryfall.com/docs/api/cards#card-face-objects
@@ -100,13 +124,29 @@ struct ScryfallCardFaceObject {
pub artist_id: Option<Uuid>, // UUID 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 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")] #[serde(rename = "color_identity")]
pub colour_identity: Vec<Colour>, pub colour_identity: Option<Vec<Colour>>,
#[serde(rename = "color_indicator")] #[serde(rename = "color_indicator")]
pub colour_indicator: Option<Vec<Colour>>, pub colour_indicator: Option<Vec<Colour>>,
#[serde(rename = "colors")] #[serde(rename = "colors")]
pub colours: Option<Vec<Colour>>, pub colours: Option<Vec<Colour>>,
pub defence: Option<String>, pub defence: Option<String>,
// TODO: Complete pub flavour_text: Option<String>,
pub illustration_id: Option<Uuid>,
pub image_uris: Option<ImageURIs>,
pub layout: Option<String>,
pub loyalty: Option<String>,
pub mana_cost: Option<String>,
pub name: String,
pub object: String,
pub oracle_id: Option<Uuid>,
pub oracle_text: Option<String>,
pub power: Option<String>,
pub printed_name: Option<String>,
pub printed_text: Option<String>,
pub printed_type_line: Option<String>,
pub toughness: Option<String>,
pub type_line: Option<String>,
pub watermark: Option<String>
} }
// https://scryfall.com/docs/api/cards#related-card-objects // https://scryfall.com/docs/api/cards#related-card-objects
@@ -115,7 +155,7 @@ struct ScryfallCardFaceObject {
struct ScryfallRelatedCardObject { struct ScryfallRelatedCardObject {
pub id: Uuid, pub id: Uuid,
pub object: String, // Always "related_card" pub object: String, // Always "related_card"
pub component: String, // One of token, meld_part, meld_result, combo_piece pub component: Component,
pub name: String, pub name: String,
pub type_line: String, pub type_line: String,
pub uri: String // URI pub uri: String // URI
@@ -133,6 +173,10 @@ enum Colour {
Red, Red,
#[serde(rename = "G")] #[serde(rename = "G")]
Green, 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)] #[derive(Deserialize, Debug)]
@@ -214,7 +258,7 @@ enum Frame {
#[serde(rename = "2003")] #[serde(rename = "2003")]
OhThree, OhThree,
#[serde(rename = "2015")] #[serde(rename = "2015")]
OhFifteen, Fifteen,
#[serde(rename = "future")] #[serde(rename = "future")]
Future Future
} }
@@ -270,7 +314,9 @@ enum FrameEffect {
#[serde(rename = "upsidedowndfc")] #[serde(rename = "upsidedowndfc")]
UpsideDownDFC, UpsideDownDFC,
#[serde(rename = "spree")] #[serde(rename = "spree")]
Spree Spree,
#[serde(rename = "fullart")]
FullArt
} }
#[allow(dead_code)] #[allow(dead_code)]
@@ -281,7 +327,11 @@ enum Game {
#[serde(rename = "mtgo")] #[serde(rename = "mtgo")]
MTGO, MTGO,
#[serde(rename = "arena")] #[serde(rename = "arena")]
Arena Arena,
#[serde(rename = "astral")]
Astral,
#[serde(rename = "sega")]
Sega
} }
#[allow(dead_code)] #[allow(dead_code)]
@@ -336,11 +386,50 @@ enum Rarity {
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,
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use std::path::PathBuf; use std::path::PathBuf;
use std::fs; use std::fs;
use std::io::{BufRead, BufReader};
#[test] #[test]
fn deserialise_nissa() { fn deserialise_nissa() {
@@ -348,8 +437,7 @@ mod tests {
f.push("test_files/nissa.json"); f.push("test_files/nissa.json");
assert!(f.exists()); assert!(f.exists());
let fc = fs::read_to_string(f).unwrap(); let fc = fs::read_to_string(f).unwrap();
let nissa: ScryfallCard = serde_json::from_str(&fc).unwrap(); let _nissa: ScryfallCard = serde_json::from_str(&fc).unwrap();
println!("{:#?}", nissa);
} }
#[test] #[test]
@@ -358,8 +446,7 @@ mod tests {
f.push("test_files/black_lotus.json"); f.push("test_files/black_lotus.json");
assert!(f.exists()); assert!(f.exists());
let fc = fs::read_to_string(f).unwrap(); let fc = fs::read_to_string(f).unwrap();
let bl: ScryfallCard = serde_json::from_str(&fc).unwrap(); let _bl: ScryfallCard = serde_json::from_str(&fc).unwrap();
println!("{:#?}", bl);
} }
#[test] #[test]
@@ -368,22 +455,37 @@ mod tests {
f.push("test_files/little_girl.json"); f.push("test_files/little_girl.json");
assert!(f.exists()); assert!(f.exists());
let fc = fs::read_to_string(f).unwrap(); let fc = fs::read_to_string(f).unwrap();
let lg: ScryfallCard = serde_json::from_str(&fc).unwrap(); let _lg: ScryfallCard = serde_json::from_str(&fc).unwrap();
println!("{:#?}", lg);
} }
#[test] #[test]
#[ignore] #[ignore]
fn try_deserialise_all_cards() { fn try_deserialise_all_cards_line_by_line() {
// TODO Actually write this funtion. // This function is uuuuuuugly and I'm sure a terrible way to go about things
// The idea will be to run over one of the Scryfall dumps of all the cards so I can be let mut f = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
// confident I've got things working. f.push("test_files/all-cards.json");
// I noticed that Scryfall very helpfully provides their dumps with 1 card per line. 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");
// So using something like the link below should hopefully mean I don't have to load in the let ac = fs::File::open(f).unwrap();
// whole 2+GB file all at once let reader = BufReader::new(ac);
// https://doc.rust-lang.org/std/io/trait.BufRead.html#method.lines for line in reader.lines().skip(1) {
// I think that's what it should do at least. I bet it'll still take a while tho. let mut line = line.unwrap();
// If that doesn't work - I should try somehow use the serde from_reader function let c = line.pop().unwrap();
// I don't 100% know how I'll use that though. // this is so dumb...
if c == '}' {
line.push('}');
}
if line.len() < 1 { continue };
let a_card: Result<ScryfallCard, serde_json::Error> = serde_json::from_str(&line.as_ref());
match a_card {
Err(error) => {
println!("{:#?}", line);
println!("{:#?}", error);
panic!();
},
Ok(_) => (),
}
}
} }
} }