use super::{midpoint, AskBook, BidBook};
use crate::{config::HttpsConfig, Error, Price, PriceQuantity, TradingPair};
use iqhttp::{HttpsClient, Query};
use serde::{de, Deserialize, Serialize};
use std::{
fmt::{self, Display},
str::FromStr,
};
pub const API_HOST: &str = "partner.gdac.com";
pub struct GdacSource {
https_client: HttpsClient,
}
impl GdacSource {
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<Price, Error> {
let mut query = Query::new();
query.add("pair".to_owned(), pair.percent_encode());
let api_response: Quote = self
.https_client
.get_json("/v0.4/public/orderbook", &query)
.await?;
midpoint(&api_response)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Quote {
pub ask: Vec<PricePoint>,
pub bid: Vec<PricePoint>,
}
impl AskBook for Quote {
fn asks(&self) -> Result<Vec<PriceQuantity>, Error> {
self.ask
.iter()
.map(|p| {
p.volume
.parse()
.map(|quantity| PriceQuantity {
price: p.price,
quantity,
})
.map_err(Into::into)
})
.collect()
}
}
impl BidBook for Quote {
fn bids(&self) -> Result<Vec<PriceQuantity>, Error> {
self.bid
.iter()
.map(|p| {
p.volume
.parse()
.map(|quantity| PriceQuantity {
price: p.price,
quantity,
})
.map_err(Into::into)
})
.collect()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PricePoint {
pub price: Price,
pub volume: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct ErrorResponse {
pub code: ErrorCode,
pub data: serde_json::Value,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ErrorCode {
InternalError,
Unavailable,
Other(String),
}
impl Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
ErrorCode::InternalError => "__internal_error__",
ErrorCode::Unavailable => "__unavailable__",
ErrorCode::Other(other) => other.as_ref(),
})
}
}
impl FromStr for ErrorCode {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
Ok(match s {
"__internal_error__" => ErrorCode::InternalError,
"__unavailable__" => ErrorCode::Unavailable,
other => ErrorCode::Other(other.to_owned()),
})
}
}
impl std::error::Error for ErrorCode {}
impl<'de> Deserialize<'de> for ErrorCode {
fn deserialize<D: de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use de::Error;
let s = String::deserialize(deserializer)?;
s.parse().map_err(D::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::GdacSource;
#[tokio::test]
#[ignore]
async fn trading_pairs_ok() {
let pair = "LUNA/KRW".parse().unwrap();
let _price = GdacSource::new(&Default::default())
.unwrap()
.trading_pairs(&pair)
.await
.unwrap();
}
#[tokio::test]
#[ignore]
async fn trading_pairs_404() {
let pair = "N/A".parse().unwrap();
let _err = GdacSource::new(&Default::default())
.unwrap()
.trading_pairs(&pair)
.await
.err()
.unwrap();
}
}