use super::{midpoint, AskBook, BidBook};
use crate::{config::HttpsConfig, Error, Price, PriceQuantity, TradingPair};
use iqhttp::{HttpsClient, Query};
use rust_decimal::Decimal;
use serde::{de, Deserialize, Serialize};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub const API_HOST: &str = "api.gopax.co.kr";
pub struct GopaxSource {
https_client: HttpsClient,
}
impl GopaxSource {
#[allow(clippy::new_without_default)]
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 query = Query::new();
let api_response: Response = self
.https_client
.get_json(
&format!("/trading-pairs/{}-{}/book", pair.0, pair.1),
&query,
)
.await?;
midpoint(&api_response)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Response {
pub sequence: u64,
pub bid: Vec<PricePoint>,
pub ask: Vec<PricePoint>,
}
impl AskBook for Response {
fn asks(&self) -> Result<Vec<PriceQuantity>, Error> {
Ok(self
.ask
.iter()
.map(|p| PriceQuantity {
price: Price::new(p.price).unwrap(),
quantity: p.volume,
})
.collect())
}
}
impl BidBook for Response {
fn bids(&self) -> Result<Vec<PriceQuantity>, Error> {
Ok(self
.bid
.iter()
.map(|p| PriceQuantity {
price: Price::new(p.price).unwrap(),
quantity: p.volume,
})
.collect())
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PricePoint {
pub id: String,
#[serde(deserialize_with = "deserialize_decimal")]
pub price: Decimal,
#[serde(deserialize_with = "deserialize_decimal")]
pub volume: Decimal,
#[serde(deserialize_with = "deserialize_timestamp")]
pub timestamp: SystemTime,
}
fn deserialize_decimal<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
where
D: de::Deserializer<'de>,
{
let value = f64::deserialize(deserializer)?;
value.to_string().parse().map_err(de::Error::custom)
}
fn deserialize_timestamp<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
where
D: de::Deserializer<'de>,
{
String::deserialize(deserializer)
.and_then(|s| s.parse().map_err(de::Error::custom))
.map(|unix_secs| UNIX_EPOCH + Duration::from_secs(unix_secs))
}
#[cfg(test)]
mod tests {
use super::GopaxSource;
#[tokio::test]
#[ignore]
async fn trading_pairs_ok() {
let pair = "LUNA/KRW".parse().unwrap();
let _quote = GopaxSource::new(&Default::default())
.unwrap()
.trading_pairs(&pair)
.await
.unwrap();
}
#[tokio::test]
#[ignore]
async fn trading_pairs_404() {
let pair = "N/A".parse().unwrap();
let quote = GopaxSource::new(&Default::default())
.unwrap()
.trading_pairs(&pair)
.await;
assert!(quote.is_err());
}
}