1use super::Price;
5use crate::{
6 config::HttpsConfig,
7 error::{Error, ErrorKind},
8 prelude::*,
9 Currency, TradingPair,
10};
11use iqhttp::{HttpsClient, Query};
12use rust_decimal::Decimal;
13use serde::{Deserialize, Serialize};
14
15pub const API_HOST: &str = "quotation-api-cdn.dunamu.com";
19
20pub struct DunamuSource {
22 https_client: HttpsClient,
23}
24
25impl DunamuSource {
26 #[allow(clippy::new_without_default)]
28 pub fn new(config: &HttpsConfig) -> Result<Self, Error> {
29 let https_client = config.new_client(API_HOST)?;
30 Ok(Self { https_client })
31 }
32
33 pub async fn trading_pairs(&self, pair: &TradingPair) -> Result<Price, Error> {
35 if pair.0 != Currency::Krw && pair.1 != Currency::Krw {
36 fail!(ErrorKind::Currency, "trading pair must be with KRW");
37 }
38
39 let mut query = Query::new();
40 query.add("codes", format!("FRX.{}{}", pair.0, pair.1));
41
42 let api_response: Response = self
43 .https_client
44 .get_json("/v1/forex/recent", &query)
45 .await?;
46 let price: Decimal = api_response[0].base_price.to_string().parse()?;
47 Ok(Price::new(price)?)
48 }
49}
50
51pub type Response = Vec<ResponseElement>;
53#[derive(Serialize, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct ResponseElement {
57 code: String,
58 currency_code: String,
59 currency_name: String,
60 country: String,
61 name: String,
62 date: String,
63 time: String,
64 recurrence_count: i64,
65 base_price: f64,
66 opening_price: f64,
67 high_price: f64,
68 low_price: f64,
69 change: String,
70 change_price: f64,
71 cash_buying_price: f64,
72 cash_selling_price: f64,
73 tt_buying_price: f64,
74 tt_selling_price: f64,
75 tc_buying_price: Option<serde_json::Value>,
76 fc_selling_price: Option<serde_json::Value>,
77 exchange_commission: f64,
78 us_dollar_rate: f64,
79 #[serde(rename = "high52wPrice")]
80 high52_w_price: f64,
81 #[serde(rename = "high52wDate")]
82 high52_w_date: String,
83 #[serde(rename = "low52wPrice")]
84 low52_w_price: f64,
85 #[serde(rename = "low52wDate")]
86 low52_w_date: String,
87 currency_unit: i64,
88 provider: String,
89 timestamp: i64,
90 id: i64,
91 created_at: String,
92 modified_at: String,
93 change_rate: f64,
94 signed_change_price: f64,
95 signed_change_rate: f64,
96}
97
98#[cfg(test)]
99mod tests {
100 use super::DunamuSource;
101
102 #[tokio::test]
104 #[ignore]
105 async fn trading_pairs_ok() {
106 let pair = "KRW/USD".parse().unwrap();
107 let _response = DunamuSource::new(&Default::default())
108 .unwrap()
109 .trading_pairs(&pair)
110 .await
111 .unwrap();
112 }
113
114 #[tokio::test]
116 #[ignore]
117 async fn trading_pairs_404() {
118 let pair = "N/A".parse().unwrap();
119
120 let _err = DunamuSource::new(&Default::default())
122 .unwrap()
123 .trading_pairs(&pair)
124 .await
125 .err()
126 .unwrap();
127 }
128}