use scryfall for mtg searcher

This commit is contained in:
Adrian Malacoda 2018-05-06 03:20:02 -05:00
parent c7235acdef
commit 60efb9f527
3 changed files with 28 additions and 73 deletions

View File

@ -13,3 +13,4 @@ serde_json = "1.0.17"
retry = { git = "https://github.com/jimmycuadra/retry", rev = "3fa812e650d64ede61ea243fb83ef1a222ff0f84" } retry = { git = "https://github.com/jimmycuadra/retry", rev = "3fa812e650d64ede61ea243fb83ef1a222ff0f84" }
mopa = "0.2.2" mopa = "0.2.2"
linked-hash-map = "0.5.1" linked-hash-map = "0.5.1"
scryfall = { git = "http://gitlab.monarch-pass.net/malacoda/scryfall.git" }

View File

@ -3,6 +3,7 @@ extern crate select;
extern crate serde_json; extern crate serde_json;
extern crate retry; extern crate retry;
extern crate linked_hash_map; extern crate linked_hash_map;
extern crate scryfall;
#[macro_use] #[macro_use]
extern crate mopa; extern crate mopa;

View File

@ -1,45 +1,27 @@
use Link; use Link;
use searchers::Searcher; use searchers::Searcher;
use hyper; use scryfall;
use hyper::Client; use scryfall::{Scryfall, Card};
use serde_json;
use serde_json::Value;
use retry; use retry;
use retry::retry; use retry::retry;
use retry::delay::Fixed; use retry::delay::Fixed;
use std; use std;
use std::io::Read;
//use std::any::Any;
use mopa::Any; use mopa::Any;
const NUM_RETRIES: usize = 10; const NUM_RETRIES: usize = 10;
const RETRY_WAIT_MILLIS: u64 = 500; const RETRY_WAIT_MILLIS: u64 = 500;
#[derive(Debug)] impl Link for Card {
pub struct MtgCard {
pub name: String,
pub cost: Option<String>,
pub typeline: String,
pub rules: Option<String>,
pub flavor: Option<String>,
pub power: Option<String>,
pub toughness: Option<String>,
pub url: String,
pub image_url: String
}
impl Link for MtgCard {
fn label (&self) -> &str { fn label (&self) -> &str {
&self.name &self.name
} }
fn url (&self) -> &str { fn url (&self) -> &str {
&self.url &self.uri
} }
fn as_any (&self) -> &Any { fn as_any (&self) -> &Any {
@ -48,69 +30,45 @@ impl Link for MtgCard {
} }
pub struct MtgSearcher { pub struct MtgSearcher {
client: Client scryfall: Scryfall
} }
impl MtgSearcher { impl MtgSearcher {
pub fn new () -> MtgSearcher { pub fn new () -> MtgSearcher {
MtgSearcher { MtgSearcher {
client: Client::new() scryfall: Scryfall::new()
} }
} }
fn do_search (&self, name: &str) -> Result<String, Error> { fn do_search (&self, query: &str) -> Result<Option<Card>, Error> {
let mut contents = String::new(); let mut result = retry(Fixed::from_millis(RETRY_WAIT_MILLIS).take(NUM_RETRIES), || {
let api_url = &format!("https://api.magicthegathering.io/v1/cards?name={}", name); self.scryfall.search(query, None, 0)
let mut response = retry(Fixed::from_millis(RETRY_WAIT_MILLIS).take(NUM_RETRIES), || {
self.client.get(api_url).send()
})?; })?;
response.read_to_string(&mut contents)?;
Result::Ok(contents) Ok(if !result.data.is_empty() {
Some(result.data.remove(0))
} else {
None
})
} }
} }
fn parse_entry (page: String) -> Result<MtgCard, Error> { impl Searcher<Card> for MtgSearcher {
let parsed: Value = serde_json::from_str(&page)?; fn exact_search (&self, name: &str) -> Option<Card> {
for entry in parsed["cards"].as_array().unwrap_or(&vec![]) { let search = format!(r#"!{}"#, name);
if entry["imageUrl"].as_str().is_some() { self.do_search(&search).unwrap_or(None)
let card_name = entry["name"].as_str().map(String::from).expect("expected name in json data");
let card_url = format!("http://magiccards.info/query?q=!{}", card_name.replace(" ", "+"));
return Result::Ok(MtgCard {
name: card_name,
cost: entry["manaCost"].as_str().map(String::from),
typeline: entry["type"].as_str().map(String::from).expect("expected type in json data"),
rules: entry["text"].as_str().map(String::from),
flavor: entry["flavor"].as_str().map(String::from),
power: entry["power"].as_str().map(String::from),
toughness: entry["toughness"].as_str().map(String::from),
url: card_url,
image_url: entry["imageUrl"].as_str().map(String::from).expect("expected image url in json data")
});
}
}
Result::Err(Error::Other(String::from("No card info found")))
}
impl Searcher<MtgCard> for MtgSearcher {
fn exact_search (&self, name: &str) -> Option<MtgCard> {
let search = format!(r#""{}""#, name);
self.do_search(&search).and_then(parse_entry).ok()
} }
} }
#[derive(Debug)] #[derive(Debug)]
enum Error { enum Error {
Http(hyper::error::Error), Client(scryfall::Error),
Io(std::io::Error), Io(std::io::Error),
Json(serde_json::Error),
Other(String)
} }
impl From<hyper::error::Error> for Error { impl From<scryfall::Error> for Error {
fn from (error: hyper::error::Error) -> Error { fn from (error: scryfall::Error) -> Error {
Error::Http(error) Error::Client(error)
} }
} }
@ -120,17 +78,12 @@ impl From<std::io::Error> for Error {
} }
} }
impl From<serde_json::Error> for Error {
fn from (error: serde_json::Error) -> Error {
Error::Json(error)
}
}
impl From<retry::Error<hyper::Error>> for Error { impl From<retry::Error<scryfall::Error>> for Error {
fn from (err: retry::Error<hyper::Error>) -> Error { fn from (err: retry::Error<scryfall::Error>) -> Error {
match err { match err {
retry::Error::Operation { error, total_delay, tries } => { retry::Error::Operation { error, total_delay, tries } => {
Error::Http(error) Error::Client(error)
} }
} }
} }