switchboard_utils/exchanges/
coinbase.rs1use crate::*;
2
3use chrono::{DateTime, Utc};
4use futures_util::TryFutureExt;
5use reqwest::{header, Client};
6use serde::Deserialize;
7
8#[derive(Debug, Clone, Deserialize)]
9pub struct CoinbaseTickerInfo {
10 pub ask: Decimal,
11 pub bid: Decimal,
12 pub volume: Decimal,
13 pub trade_id: u64,
14 pub price: Decimal,
15 pub size: Decimal,
16 pub time: DateTime<Utc>,
17}
18
19pub struct CoinbaseApi {}
20
21impl CoinbaseApi {
22 fn get_ticker_url(ticker: &str, url: Option<&str>) -> String {
24 format!(
25 "{}/products/{}/ticker",
26 url.unwrap_or("https://api.exchange.coinbase.com"),
27 ticker
28 )
29 }
30
31 pub async fn fetch_ticker(
32 ticker: &str,
33 url: Option<&str>,
34 ) -> Result<CoinbaseTickerInfo, SbError> {
35 let client = Client::new();
36 let ticker_url = CoinbaseApi::get_ticker_url(ticker, url);
37
38 let response: CoinbaseTickerInfo = client
39 .get(&ticker_url)
40 .header(header::USER_AGENT, "null")
41 .send()
42 .and_then(|r| r.json())
43 .await
44 .map_err(handle_reqwest_err)?;
45
46 Ok(response)
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 const MOCK_COINBASE_RESPONSE: &str = r#"{"ask":"27582.21","bid":"27581.23","volume":"9812.9333157","trade_id":566366195,"price":"27581.23","size":"0.40855232","time":"2023-10-04T20:04:04.969312Z"}"#;
55
56 #[tokio::test]
57 async fn test_coinbase_api() {
58 let mut server = mockito::Server::new();
60 let server_url = server.url();
61
62 let mock = server
64 .mock("GET", "/products/BTC-USD/ticker")
65 .with_status(201)
66 .with_header("content-type", "application/json")
67 .with_body(MOCK_COINBASE_RESPONSE)
68 .create();
69
70 let ticker = CoinbaseApi::fetch_ticker("BTC-USD", Some(server_url.as_str()))
71 .await
72 .unwrap();
73
74 mock.assert();
76
77 assert_eq!(ticker.ask, Decimal::from_str("27582.21").unwrap());
78 }
79}