bothan_bitfinex/api/rest.rs
1//! Bitfinex REST API client implementation.
2//!
3//! This module provides the [`RestApi`] for interacting with the Bitfinex REST API.
4//! It enables fetching of market data, such as ticker information for both spot and funding markets,
5//! and is used internally to implement the [`AssetInfoProvider`] trait for asset workers.
6//!
7//! This module provides:
8//!
9//! - HTTP client for making requests to Bitfinex REST endpoints
10//! - Fetches ticker data for specified symbols via REST API calls
11//! - Processes ticker responses and transforms them into [`AssetInfo`] for use in workers
12//! - Handles error responses and data validation
13//! - Supports both spot and funding ticker data retrieval
14
15use bothan_lib::types::AssetInfo;
16use bothan_lib::worker::rest::AssetInfoProvider;
17use reqwest::{Client, Url};
18use rust_decimal::Decimal;
19
20use crate::api::error::ProviderError;
21use crate::api::msg::ticker::Ticker;
22
23pub const DEFAULT_URL: &str = "https://api-pub.bitfinex.com/v2/";
24
25/// A client for interacting with the Bitfinex REST API.
26///
27/// The `RestApi` provides methods to fetch ticker data from the Bitfinex API
28/// and implements the [`AssetInfoProvider`] trait for integration with asset workers.
29///
30/// # Examples
31///
32/// ```rust,no_run
33/// use bothan_bitfinex::api::rest::RestApi;
34/// use reqwest::Client;
35/// use url::Url;
36///
37/// #[tokio::main]
38/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
39/// let url = Url::parse("https://api-pub.bitfinex.com/v2/").unwrap();
40/// let client = Client::new();
41/// let api = RestApi::new(url, client);
42///
43/// // Fetch ticker data for specific symbols
44/// let tickers = vec!["tBTCUSD", "tETHUSD"];
45/// let result = api.get_tickers(&tickers).await?;
46/// Ok(())
47/// }
48/// ```
49pub struct RestApi {
50 /// The base URL for the Bitfinex API.
51 url: Url,
52 /// The HTTP client for making requests.
53 client: Client,
54}
55
56impl RestApi {
57 /// Creates a new `RestApi` instance with the specified URL and HTTP client.
58 ///
59 /// # Parameters
60 ///
61 /// - `url`: The base URL for the Bitfinex API
62 /// - `client`: The HTTP client for making requests
63 ///
64 /// # Returns
65 ///
66 /// A new `RestApi` instance configured with the provided URL and client.
67 pub fn new(url: Url, client: Client) -> Self {
68 Self { url, client }
69 }
70
71 /// Fetches ticker data for the specified symbols from the Bitfinex API.
72 ///
73 /// This method makes a REST API call to the Bitfinex tickers endpoint to retrieve
74 /// current market data for the specified trading pairs. The response includes
75 /// both spot and funding ticker information depending on the symbol type.
76 ///
77 /// # Parameters
78 ///
79 /// - `tickers`: A slice of symbol identifiers to fetch ticker data for
80 ///
81 /// # Returns
82 ///
83 /// Returns a `Result` containing a vector of `Ticker` instances on success,
84 /// or a `reqwest::Error` if the request fails.
85 ///
86 /// # Examples
87 ///
88 /// ```rust
89 /// use bothan_bitfinex::api::rest::RestApi;
90 /// use reqwest::Client;
91 /// use url::Url;
92 ///
93 /// #[tokio::main]
94 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
95 /// let url = Url::parse("https://api-pub.bitfinex.com/v2/").unwrap();
96 /// let client = Client::new();
97 /// let api = RestApi::new(url, client);
98 ///
99 /// let symbols = vec!["tBTCUSD", "fUSD"];
100 /// let tickers = api.get_tickers(&symbols).await?;
101 ///
102 /// for ticker in tickers {
103 /// println!("Symbol: {}, Price: {}", ticker.symbol(), ticker.price());
104 /// }
105 ///
106 /// Ok(())
107 /// }
108 /// ```
109 ///
110 /// # Errors
111 ///
112 /// Returns a `reqwest::Error` if:
113 /// - The HTTP request fails due to network issues
114 /// - The API returns an error response
115 /// - The response cannot be parsed as JSON
116 pub async fn get_tickers<T: AsRef<str>>(
117 &self,
118 tickers: &[T],
119 ) -> Result<Vec<Ticker>, reqwest::Error> {
120 let url = format!("{}/tickers", self.url);
121 let symbols = tickers
122 .iter()
123 .map(|t| t.as_ref())
124 .collect::<Vec<&str>>()
125 .join(",");
126 let params = vec![("symbols", symbols)];
127
128 let resp = self.client.get(&url).query(¶ms).send().await?;
129 resp.error_for_status_ref()?;
130 resp.json().await
131 }
132}
133
134#[async_trait::async_trait]
135impl AssetInfoProvider for RestApi {
136 type Error = ProviderError;
137
138 /// Fetches asset information for the specified asset IDs.
139 ///
140 /// This method retrieves ticker data from the Bitfinex API for the given asset IDs
141 /// and transforms the data into [`AssetInfo`] instances for use in asset workers.
142 /// The method handles both spot and funding ticker types automatically.
143 ///
144 /// # Parameters
145 ///
146 /// - `ids`: A slice of asset identifiers to fetch information for
147 ///
148 /// # Returns
149 ///
150 /// Returns a `Result` containing a vector of `AssetInfo` instances on success,
151 /// or a `ProviderError` if the request fails or contains invalid data.
152 ///
153 /// # Errors
154 ///
155 /// Returns a `ProviderError` if:
156 /// - The API request fails (`RequestError`)
157 /// - The ticker data contains invalid values such as NaN (`InvalidValue`)
158 async fn get_asset_info(&self, ids: &[String]) -> Result<Vec<AssetInfo>, Self::Error> {
159 let timestamp = chrono::Utc::now().timestamp();
160 self.get_tickers(ids)
161 .await?
162 .into_iter()
163 .map(|t| {
164 let price =
165 Decimal::from_f64_retain(t.price()).ok_or(ProviderError::InvalidValue)?;
166 Ok(AssetInfo::new(t.symbol().to_string(), price, timestamp))
167 })
168 .collect()
169 }
170}