Skip to main content

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(&params).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}