Skip to content

Commit 1e68fca

Browse files
committed
fix: parse new JSONL bulk data format + add missig fields and variants
1 parent 45b441b commit 1e68fca

10 files changed

Lines changed: 114 additions & 122 deletions

File tree

Cargo.lock

Lines changed: 61 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ unknown_variants_slim = []
2525
bin = ["tokio/macros", "tokio/rt-multi-thread"]
2626

2727
[dependencies]
28+
async-compression = {version = "0.4.43", features = ["gzip", "tokio"]}
2829
async-trait = "0.1.81"
2930
bytes = "1.10.0"
3031
cfg-if = "1"
3132
chrono = { version = "0.4", features = ["serde"] }
33+
flate2 = "1.1.9"
3234
futures = "0.3.30"
3335
futures-util = {version = "0.3.31"}
3436
heck = { version = "0.5", optional = true }
@@ -44,7 +46,7 @@ static_assertions = "1"
4446
thiserror = "1"
4547
tinyvec = "1"
4648
tokio = { version = "1", default-features = false, features = ["sync", "fs"] }
47-
tokio-stream = {version = "0.1.17", features = ["sync"]}
49+
tokio-stream = {version = "0.1.17", features = ["sync", "io-util"]}
4850
tokio-util = {version = "0.7.13", features = ["io-util", "io"]}
4951
url = { version = "2", features = ["serde"] }
5052
uuid = { version = "1", features = ["serde"] }

scripts/test-features.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22

33
set -ueo pipefail
44

src/bulk.rs

Lines changed: 24 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,31 @@
1818
use std::io::BufReader;
1919
use std::path::Path;
2020

21+
use async_compression::tokio::bufread::GzipDecoder;
2122
use cfg_if::cfg_if;
2223
use chrono::{DateTime, Utc};
2324
use futures::Stream;
2425
use serde::de::DeserializeOwned;
2526
use serde::Deserialize;
27+
use tokio::io::AsyncBufReadExt;
2628
use tokio::io::AsyncRead;
29+
use tokio_stream::wrappers::LinesStream;
2730
use tokio_stream::StreamExt;
2831
use tokio_util::io::StreamReader;
2932
use uuid::Uuid;
3033

3134
cfg_if! {
3235
if #[cfg(not(feature = "bulk_caching"))] {
3336
use bytes::Buf;
37+
use flate2::read::GzDecoder;
3438
}
3539
}
3640

3741
use crate::card::Card;
3842
use crate::ruling::Ruling;
3943
use crate::uri::Uri;
40-
use crate::util::{streaming_deserializer, BULK_DATA_URL};
44+
use crate::util::BULK_DATA_URL;
45+
use crate::Error;
4146

4247
/// Scryfall provides daily exports of our card data in bulk files. Each of
4348
/// these files is represented as a bulk_data object via the API. URLs for files
@@ -82,24 +87,14 @@ pub struct BulkDataFile<T> {
8287
pub description: String,
8388

8489
/// The URI that hosts this bulk file for fetching.
85-
pub download_uri: Uri<Vec<T>>,
90+
pub jsonl_download_uri: Uri<Vec<T>>,
8691

8792
/// The time when this file was last updated.
8893
pub updated_at: DateTime<Utc>,
8994

9095
/// The size of this file in integer bytes.
9196
pub compressed_size: Option<usize>,
9297

93-
/// The MIME type of this file.
94-
pub content_type: String,
95-
96-
/// The Content-Encoding encoding that will be used to transmit this file
97-
/// when you download it.
98-
pub content_encoding: String,
99-
100-
/// The byte size of the bulk file.
101-
pub size: usize,
102-
10398
#[cfg(test)]
10499
#[serde(rename = "object")]
105100
_object: String,
@@ -135,28 +130,29 @@ impl<T: DeserializeOwned> BulkDataFile<T> {
135130

136131
let file = tokio::fs::File::open(&cache_path).await?;
137132

138-
Ok(tokio::io::BufReader::new(file))
133+
let raw_reader = tokio::io::BufReader::new(file);
134+
Ok(async_compression::tokio::bufread::GzipDecoder::new(raw_reader))
139135
}
140136
} else {
141137
async fn get_reader(&self) -> crate::Result<BufReader<impl std::io::Read + Send>> {
142138

143-
let response = self.download_uri.fetch_raw().await?;
139+
let response = self.jsonl_download_uri.fetch_raw().await?;
144140
let body = response.bytes().await.map_err(|e| {
145-
crate::Error::ReqwestError { error: Box::new(e), url: self.download_uri.inner().clone() }
141+
crate::Error::ReqwestError { error: Box::new(e), url: self.jsonl_download_uri.inner().clone() }
146142
})?;
147-
Ok(BufReader::new(body.reader()))
143+
Ok(BufReader::new(GzDecoder::new(body.reader())))
148144
}
149145

150146
async fn get_async_reader(&self) -> crate::Result<impl AsyncRead> {
151-
let response = self.download_uri.fetch_raw().await?;
147+
let response = self.jsonl_download_uri.fetch_raw().await?;
152148
let stream = response.bytes_stream()
153149
.map(|bytes_result| {
154150
bytes_result
155-
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
151+
.map_err(std::io::Error::other)
156152
// .map(|bytes| bytes.to_vec())
157153
});
158154

159-
Ok(StreamReader::new(stream))
155+
Ok(GzipDecoder::new(StreamReader::new(stream)))
160156
}
161157
}
162158
}
@@ -190,14 +186,21 @@ impl<T: DeserializeOwned> BulkDataFile<T> {
190186
T: Send + 'static,
191187
{
192188
let reader = self.get_async_reader().await?;
193-
Ok(streaming_deserializer::create(reader))
189+
190+
Ok(
191+
LinesStream::new(tokio::io::BufReader::new(reader).lines()).map(|line_result| {
192+
line_result
193+
.map_err(Error::from)
194+
.and_then(|line| serde_json::from_str::<T>(&line).map_err(Error::from))
195+
}),
196+
)
194197
}
195198

196199
/// Downloads this file, saving it to `path`. Overwrites the file if it
197200
/// already exists.
198201
pub async fn download(&self, path: impl AsRef<Path>) -> crate::Result<()> {
199202
let path = path.as_ref();
200-
let response = self.download_uri.fetch_raw().await?;
203+
let response = self.jsonl_download_uri.fetch_raw().await?;
201204

202205
let body = response
203206
.bytes_stream()
@@ -256,8 +259,6 @@ pub async fn rulings() -> crate::Result<impl Stream<Item = crate::Result<Ruling>
256259
mod tests {
257260
use futures::StreamExt;
258261

259-
use crate::util::streaming_deserializer;
260-
261262
#[tokio::test]
262263
#[ignore]
263264
async fn oracle_cards() {
@@ -302,31 +303,4 @@ mod tests {
302303
card.unwrap();
303304
}
304305
}
305-
306-
#[tokio::test]
307-
async fn test_parse_list() {
308-
use crate::ruling::Ruling;
309-
let s = r#"[
310-
{
311-
"object": "ruling",
312-
"oracle_id": "0004ebd0-dfd6-4276-b4a6-de0003e94237",
313-
"source": "wotc",
314-
"published_at": "2004-10-04",
315-
"comment": "If there are two of these on the battlefield, they do not add together. The result is that only two permanents can be untapped."
316-
},
317-
{
318-
"object": "ruling",
319-
"oracle_id": "0007c283-5b7a-4c00-9ca1-b455c8dff8c3",
320-
"source": "wotc",
321-
"published_at": "2019-08-23",
322-
"comment": "The “commander tax” increases based on how many times a commander was cast from the command zone. Casting a commander from your hand doesn’t require that additional cost, and it doesn’t increase what the cost will be the next time you cast that commander from the command zone."
323-
}
324-
]"#;
325-
let mut stream =
326-
streaming_deserializer::create(s.as_bytes()).map(|r: crate::Result<Ruling>| r.unwrap());
327-
328-
while let Some(r) = stream.next().await {
329-
drop(r)
330-
}
331-
}
332306
}

src/card.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,21 @@ pub struct ImageUris {
224224
/// A small full card image. Designed for use as thumbnail or list icon.
225225
#[serde(default)]
226226
pub small: Option<Url>,
227+
/// A small thumbnail of the card image, replaces small (format: WEBP)
228+
#[serde(default)]
229+
pub thumb: Option<Url>,
230+
/// A small thumbnail of the card image, replaces small (format: WEBP)
231+
#[serde(default)]
232+
pub grid: Option<Url>,
233+
/// A small thumbnail of the card image, replaces small (format: WEBP)
234+
#[serde(default)]
235+
pub display: Option<Url>,
236+
/// A full card image with the rounded corners and the majority of the border cropped off. Replaces border_crop (format: WEBP)
237+
#[serde(default)]
238+
pub crop: Option<Url>,
239+
/// A rectangular crop of the card’s art only. Replaces `art_crop (format: WEBP)
240+
#[serde(default)]
241+
pub art: Option<Url>,
227242
}
228243

229244
/// Card objects represent individual Magic: The Gathering cards that players

src/card/layout.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ pub enum Layout {
7171
Mutate,
7272
/// Case
7373
Case,
74+
/// Cards with a prepared spell part
75+
Prepare,
76+
/// An extra card that indicates a deck type
77+
FrontCard,
7478
#[cfg_attr(
7579
docsrs,
7680
doc(cfg(any(feature = "unknown_variants", feature = "unknown_variants_slim")))

0 commit comments

Comments
 (0)