1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//! # coindesk
//! A Bitcoin price index API powered by [coindesk.com](https://coindesk.com).
//!
//! ```
//! coindesk = "0.1.0"
//! ```
//!
//! Because this is an async library you will need an async runtime like `async_std` or `tokio`.
//!
//! Example:
//! ```rust
//! use coindesk::Bitcoin;
//!
//! #[tokio::main]
//! async fn main() {
//!     let data = Bitcoin::get().await.unwrap();
//!
//!     println!("currency: {}, rate: {}, description: {}, symbol: {}", data.usd.code, data.usd.rate, data.usd.description, data.usd.symbol);
//!     println!("currency: {}, rate: {}, description: {}, symbol: {}", data.gbp.code, data.gbp.rate, data.gbp.description, data.gbp.symbol);
//!     println!("currency: {}, rate: {}, description: {}, symbol: {}", data.eur.code, data.eur.rate, data.eur.description, data.eur.symbol);
//!     println!("time: {}", data.time);
//! }
//! ```
//!
//! Output:
//! ```
//! currency: USD, rate: 57532.8599, description: United States Dollar, symbol: $
//! currency: GBP, rate: 41359.4525, description: British Pound Sterling, symbol: £
//! currency: EUR, rate: 47927.864, description: Euro, symbol: €
//! time: 2021-05-05 15:58:00 UTC
//! ```
//! The time field implements `chrono::DateTime` for integration with the rest of your project.
//!
//! Disclaimer: This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from [openexchangerates.org](https://openexchangerates.org)
//!
//! Made with ❤️ in 🦀 by Grant Handy.


use serde_json::Value;
use chrono::{DateTime, Utc};

#[derive(Clone, PartialEq, Debug)]
pub struct Currency {
    pub code: String,
    pub symbol: String,
    pub description: String,
    pub rate: f64,
}

impl Currency {
    pub fn get(ctype: &str, data: Value) -> Result<Self, Error> {
        let code = match &data["bpi"][ctype]["code"] {
            Value::String(output) => output,
            _ => return Err(Error::Format(String::from("Couldn't find code"))),
        };

        let symbol = match &data["bpi"][ctype]["symbol"] {
            Value::String(output) => output,
            _ => return Err(Error::Format(String::from("Couldn't find symbol"))),
        };

        let description = match &data["bpi"][ctype]["description"] {
            Value::String(output) => output,
            _ => return Err(Error::Format(String::from("Couldn't find description"))),
        };

        let rate = match &data["bpi"][ctype]["rate_float"] {
            Value::Number(output) => match output.as_f64() {
                Some(num) => num,
                None => return Err(Error::Format(String::from("Rate is not f64"))),
            },
            _ => return Err(Error::Format(String::from("Couldn't find rate"))),
        };

        Ok(Self {
            code: code.to_owned(),
            symbol: symbol.to_owned(),
            description: description.to_owned(),
            rate,
        })
    }
}

#[derive(Clone, PartialEq, Hash, Debug)]
pub enum Error {
    Http(String),
    Format(String),
}

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

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Error::Http(error) => {
                write!(f, "HTTP Error: {}", error)
            }
            Error::Format(error) => {
                write!(f, "Formatting Error {}", error)
            }
        }
    }
}

#[derive(Clone, PartialEq, Debug)]
pub struct Bitcoin {
    pub time: DateTime<Utc>,
    pub usd: Currency,
    pub gbp: Currency,
    pub eur: Currency,
}

impl Bitcoin {
    pub async fn get() -> Result<Self, Error> {
        let data = match surf::get("https://api.coindesk.com/v1/bpi/currentprice.json").recv_string().await {
            Ok(data) => data,
            Err(error) => return Err(Error::Http(error.to_string())),
        };

        let parsed_json: Value = match serde_json::from_str(&data) {
            Ok(parsed_json) => parsed_json,
            Err(error) => {
                return Err(Error::Format(error.to_string()));
            }
        };

        let usd = Currency::get("USD", parsed_json.clone())?;
        let gbp = Currency::get("GBP", parsed_json.clone())?;
        let eur = Currency::get("EUR", parsed_json.clone())?;

        let time = match &parsed_json["time"]["updatedISO"] {
            Value::String(output) => output,
            _ => return Err(Error::Format(String::from("Couldn't find updatedISO"))),
        };

        let time: DateTime<Utc> = match DateTime::parse_from_rfc3339(time) {
            Ok(time) => DateTime::from(time),
            Err(error) => return Err(Error::Format(format!("Couldn't format time: {}", error))),
        };

        Ok(Self {
            time,
            usd,
            gbp,
            eur,
        })
    }
}