scryfall-client 0.1.0

an wrapper for the scryfall API
mod autocomplete;
mod card_identifier;
mod client_paths;
mod collection;
mod name;
mod random;
mod search;

use lazy_static::*;
use reqwest::IntoUrl;
use serde::de::DeserializeOwned;
use serde::export::Formatter;
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use std::sync::Mutex;
use std::thread::sleep;
use std::time::{Duration, SystemTime};

#[derive(Debug)]
pub enum ClientError {
    Request(reqwest::Error),
    TextDecode(reqwest::Error),
    Deserialize(serde_json::Error),
    Serialize(serde_json::Error),
    Scryfall(ScryfallError),
}

impl std::fmt::Display for ClientError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ClientError::Request(e) => e.fmt(f),
            ClientError::TextDecode(e) => e.fmt(f),
            ClientError::Deserialize(e) => e.fmt(f),
            ClientError::Serialize(e) => e.fmt(f),
            ClientError::Scryfall(e) => format!("{:?}", e).fmt(f),
        }
    }
}

impl std::error::Error for ClientError {}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ScryfallError {
    pub object: String,
    pub code: String,
    pub status: u16,
    pub details: String,
}

pub struct Client;

impl Client {
    fn get<S: IntoUrl>(&self, path: S) -> reqwest::RequestBuilder {
        reqwest::Client::new().get(path)
    }
    fn post<S: IntoUrl>(&self, path: S) -> reqwest::RequestBuilder {
        reqwest::Client::new().post(path)
    }
    async fn send_request<T: DeserializeOwned>(
        &self,
        request: reqwest::RequestBuilder,
    ) -> Result<T, ClientError> {
        wait_for_rate_limit().await;
        let result = request.send().await.map_err(|x| ClientError::Request(x))?;
        let text = result
            .text()
            .await
            .map_err(|x| ClientError::TextDecode(x))?;
        let json: T = serde_json::from_reader(text.as_bytes()).map_err(|x| {
            let json_error: serde_json::error::Result<ScryfallError> =
                serde_json::from_reader(text.as_bytes());
            match json_error {
                Ok(scryfall) => ClientError::Scryfall(scryfall),
                _ => ClientError::Deserialize(x),
            }
        })?;
        Ok(json)
    }
}

lazy_static! {
    static ref LAST_WAIT_FOR_RATE_LIMIT_CALL: Mutex<u128> = Mutex::new(0);
}

const RATE_LIMIT_WAIT: u32 = 100;

pub fn get_unix_time() -> u128 {
    SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .unwrap()
        .as_millis()
}

pub async fn wait_for_rate_limit() -> () {
    fn wait_time(last: u128, now: u128) -> u64 {
        if now > last {
            0
        } else {
            (last - now) as u64
        }
    }
    let mut value = LAST_WAIT_FOR_RATE_LIMIT_CALL.lock().unwrap();
    sleep(Duration::from_millis(wait_time(
        value.deref().clone(),
        get_unix_time(),
    )));
    *value = get_unix_time() + RATE_LIMIT_WAIT as u128;
    ()
}