crypto_pay_api/api/exchange.rs
1use async_trait::async_trait;
2
3use crate::{
4 client::CryptoBot,
5 error::CryptoBotResult,
6 models::{APIEndpoint, APIMethod, ExchangeRate, Method},
7};
8
9use super::ExchangeRateAPI;
10
11#[async_trait]
12impl ExchangeRateAPI for CryptoBot {
13 /// Gets current exchange rates for all supported cryptocurrencies
14 ///
15 /// This method returns exchange rates between supported cryptocurrencies and target currencies.
16 /// Exchange rates are updated regularly by CryptoBot.
17 ///
18 /// # Returns
19 /// * `Ok(Vec<ExchangeRate>)` - A list of current exchange rates
20 /// * `Err(CryptoBotError)` - If the request fails
21 ///
22 /// # Exchange Rate Pairs
23 /// Exchange rates are provided for various pairs:
24 /// * Cryptocurrency to fiat (e.g., TON/USD, BTC/EUR)
25 /// * Cryptocurrency to cryptocurrency (e.g., TON/BTC, ETH/BTC)
26 /// * Test currencies are also included in testnet mode
27 ///
28 /// # Example
29 /// ```no_run
30 /// use crypto_pay_api::prelude::*;
31 ///
32 /// #[tokio::main]
33 /// async fn main() -> Result<(), CryptoBotError> {
34 /// let client = CryptoBot::builder().api_token("YOUR_API_TOKEN").build().unwrap();
35 ///
36 /// let rates = client.get_exchange_rates().await?;
37 ///
38 /// for rate in rates {
39 /// println!("Exchange Rate: {} {} = {}",
40 /// rate.source,
41 /// rate.target,
42 /// rate.rate,
43 /// );
44 /// }
45 ///
46 /// Ok(())
47 /// }
48 /// ```
49 ///
50 /// # See Also
51 /// * [ExchangeRate](struct.ExchangeRate.html) - The structure representing an exchange rate
52 async fn get_exchange_rates(&self) -> CryptoBotResult<Vec<ExchangeRate>> {
53 #[cfg(test)]
54 if let Some(rates) = &self.test_rates {
55 return Ok(rates.clone());
56 }
57
58 self.make_request(
59 &APIMethod {
60 endpoint: APIEndpoint::GetExchangeRates,
61 method: Method::GET,
62 },
63 None::<()>.as_ref(),
64 )
65 .await
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use mockito::Mock;
72 use rust_decimal_macros::dec;
73 use serde_json::json;
74
75 use crate::{
76 models::{CryptoCurrencyCode, FiatCurrencyCode},
77 utils::test_utils::TestContext,
78 };
79
80 use super::*;
81
82 impl TestContext {
83 pub fn mock_exchange_rates_response(&mut self) -> Mock {
84 self.server
85 .mock("GET", "/getExchangeRates")
86 .with_header("content-type", "application/json")
87 .with_header("Crypto-Pay-API-Token", "test_token")
88 .with_body(
89 json!({
90 "ok": true,
91 "result": [
92 {
93 "is_valid": true,
94 "is_crypto": true,
95 "is_fiat": false,
96 "source": "TON",
97 "target": "USD",
98 "rate": "3.70824926"
99 },
100 {
101 "is_valid": true,
102 "is_crypto": true,
103 "is_fiat": false,
104 "source": "DOGE",
105 "target": "EUR",
106 "rate": "0.24000835"
107 },
108 {
109 "is_valid": true,
110 "is_crypto": true,
111 "is_fiat": false,
112 "source": "USDT",
113 "target": "RUB",
114 "rate": "96.92078586"
115 },
116 {
117 "is_valid": true,
118 "is_crypto": true,
119 "is_fiat": false,
120 "source": "TON",
121 "target": "EUR",
122 "rate": "3.59048268"
123 },
124 ]
125 })
126 .to_string(),
127 )
128 .create()
129 }
130 }
131
132 #[test]
133 fn test_get_exchange_rates() {
134 let mut ctx = TestContext::new();
135 let _m = ctx.mock_exchange_rates_response();
136
137 let client = CryptoBot::builder()
138 .api_token("test_token")
139 .base_url(ctx.server.url())
140 .build()
141 .unwrap();
142
143 let result = ctx.run(async { client.get_exchange_rates().await });
144
145 println!("result: {:?}", result);
146
147 assert!(result.is_ok());
148
149 let exchange_rates = result.unwrap();
150 assert_eq!(exchange_rates.len(), 4);
151 assert_eq!(exchange_rates[0].source, CryptoCurrencyCode::Ton);
152 assert_eq!(exchange_rates[0].target, FiatCurrencyCode::Usd);
153 assert_eq!(exchange_rates[0].rate, dec!(3.70824926));
154 }
155}