use crate::{
config::HttpsConfig,
error::{Error, ErrorKind},
prelude::*,
Currency, Price, TradingPair,
};
use bytes::Buf;
use iqhttp::HttpsClient;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
pub const API_HOST: &str = "www.imf.org";
pub struct ImfSdrSource {
https_client: HttpsClient,
}
#[derive(Debug, Deserialize)]
struct ImfsdrRow {
currency: String,
price_0: Option<Price>,
price_1: Option<Price>,
price_2: Option<Price>,
price_3: Option<Price>,
price_4: Option<Price>,
}
impl ImfsdrRow {
fn response_from_best_price(&self) -> Option<Response> {
if let Some(ref price) = self.price_0 {
return Some(Response { price: *price });
}
if let Some(ref price) = self.price_1 {
return Some(Response { price: *price });
}
if let Some(ref price) = self.price_2 {
return Some(Response { price: *price });
}
if let Some(ref price) = self.price_3 {
return Some(Response { price: *price });
}
if let Some(ref price) = self.price_4 {
return Some(Response { price: *price });
}
None
}
}
impl ImfSdrSource {
pub fn new(config: &HttpsConfig) -> Result<Self, Error> {
let https_client = config.new_client(API_HOST)?;
Ok(Self { https_client })
}
pub async fn trading_pairs(&self, pair: &TradingPair) -> Result<Response, Error> {
if pair.1 != Currency::Sdr {
fail!(ErrorKind::Currency, "trading pair must be with IMF SDR");
}
let uri = format!(
"https://{}/external/np/fin/data/rms_five.aspx?tsvflag=Y",
API_HOST
);
let body = self
.https_client
.get_body(&uri, &Default::default())
.await?;
let mut imf_sdr = csv::ReaderBuilder::new()
.has_headers(false)
.flexible(true)
.delimiter(b'\t')
.from_reader(body.reader());
let mut response_row: Option<ImfsdrRow> = None;
for result in imf_sdr.records() {
let record = result.map_err(|e| {
format_err!(ErrorKind::Source, "got error with malformed csv: {}", e)
})?;
let row: Result<ImfsdrRow, csv::Error> = record.deserialize(None);
match row {
Ok(imf_sdr_row) => {
if imf_sdr_row.currency == pair.0.imf_long_name() {
response_row = Some(imf_sdr_row);
break;
}
}
Err(_e) => continue,
};
}
match response_row {
Some(resp) => Response::try_from(resp)
.map_err(|e| format_err!(ErrorKind::Parse, "{}, {}", e, pair).into()),
None => Err(format_err!(ErrorKind::Parse, "price for {} not found", pair).into()),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Response {
pub price: Price,
}
impl TryFrom<ImfsdrRow> for Response {
type Error = &'static str;
fn try_from(row: ImfsdrRow) -> Result<Self, Self::Error> {
match row.response_from_best_price() {
Some(resp) => Ok(resp),
None => Err("No price data found for for currency pair"),
}
}
}
#[cfg(test)]
mod tests {
use super::ImfSdrSource;
#[tokio::test]
#[ignore]
async fn trading_pairs_ok() {
let pair = "KRW/SDR".parse().unwrap();
let quote = ImfSdrSource::new(&Default::default())
.unwrap()
.trading_pairs(&pair)
.await
.unwrap();
dbg!("e);
}
}