fmp_rs/endpoints/
crypto.rs1use crate::{
4 client::FmpClient,
5 error::Result,
6 models::crypto::{CryptoHistorical, CryptoIntraday, CryptoQuote, CryptoSymbol},
7};
8use serde::{Deserialize, Serialize};
9
10pub struct Crypto {
12 client: FmpClient,
13}
14
15impl Crypto {
16 pub(crate) fn new(client: FmpClient) -> Self {
17 Self { client }
18 }
19
20 pub async fn get_crypto_list(&self) -> Result<Vec<CryptoSymbol>> {
38 #[derive(Serialize)]
39 struct Query<'a> {
40 apikey: &'a str,
41 }
42
43 let url = self.client.build_url("/symbol/available-cryptocurrencies");
44 self.client
45 .get_with_query(
46 &url,
47 &Query {
48 apikey: self.client.api_key(),
49 },
50 )
51 .await
52 }
53
54 pub async fn get_crypto_quote(&self, symbol: &str) -> Result<Vec<CryptoQuote>> {
76 #[derive(Serialize)]
77 struct Query<'a> {
78 apikey: &'a str,
79 }
80
81 let url = self.client.build_url(&format!("/quote/{}", symbol));
82 self.client
83 .get_with_query(
84 &url,
85 &Query {
86 apikey: self.client.api_key(),
87 },
88 )
89 .await
90 }
91
92 pub async fn get_crypto_historical(
115 &self,
116 symbol: &str,
117 from: Option<&str>,
118 to: Option<&str>,
119 ) -> Result<Vec<CryptoHistorical>> {
120 #[derive(Serialize)]
121 struct Query<'a> {
122 #[serde(skip_serializing_if = "Option::is_none")]
123 from: Option<&'a str>,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 to: Option<&'a str>,
126 apikey: &'a str,
127 }
128
129 let url = self
130 .client
131 .build_url(&format!("/historical-price-full/{}", symbol));
132
133 #[derive(Deserialize)]
134 struct Response {
135 historical: Vec<CryptoHistorical>,
136 }
137
138 let response: Response = self
139 .client
140 .get_with_query(
141 &url,
142 &Query {
143 from,
144 to,
145 apikey: self.client.api_key(),
146 },
147 )
148 .await?;
149
150 Ok(response.historical)
151 }
152
153 pub async fn get_crypto_intraday(
175 &self,
176 symbol: &str,
177 interval: &str,
178 ) -> Result<Vec<CryptoIntraday>> {
179 #[derive(Serialize)]
180 struct Query<'a> {
181 apikey: &'a str,
182 }
183
184 let url = self
185 .client
186 .build_url(&format!("/historical-chart/{}/{}", interval, symbol));
187 self.client
188 .get_with_query(
189 &url,
190 &Query {
191 apikey: self.client.api_key(),
192 },
193 )
194 .await
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[tokio::test]
204 #[ignore = "requires FMP API key"]
205 async fn test_get_crypto_list() {
206 let client = FmpClient::new().unwrap();
207 let result = client.crypto().get_crypto_list().await;
208 assert!(result.is_ok());
209 let cryptos = result.unwrap();
210 assert!(!cryptos.is_empty());
211 }
212
213 #[tokio::test]
214 #[ignore = "requires FMP API key"]
215 async fn test_get_crypto_quote() {
216 let client = FmpClient::new().unwrap();
217 let result = client.crypto().get_crypto_quote("BTCUSD").await;
218 assert!(result.is_ok());
219 let quotes = result.unwrap();
220 assert!(!quotes.is_empty());
221 assert!(quotes[0].price.is_some());
222 }
223
224 #[tokio::test]
225 #[ignore = "requires FMP API key"]
226 async fn test_get_crypto_historical() {
227 let client = FmpClient::new().unwrap();
228 let result = client
229 .crypto()
230 .get_crypto_historical("BTCUSD", None, None)
231 .await;
232 assert!(result.is_ok());
233 let history = result.unwrap();
234 assert!(!history.is_empty());
235 }
236
237 #[tokio::test]
238 #[ignore = "requires FMP API key"]
239 async fn test_get_crypto_intraday() {
240 let client = FmpClient::new().unwrap();
241 let result = client.crypto().get_crypto_intraday("BTCUSD", "5min").await;
242 assert!(result.is_ok());
243 let intraday = result.unwrap();
244 assert!(!intraday.is_empty());
245 }
246
247 #[tokio::test]
249 #[ignore = "requires FMP API key"]
250 async fn test_crypto_historical_with_dates() {
251 let client = FmpClient::new().unwrap();
252 let result = client
253 .crypto()
254 .get_crypto_historical("BTCUSD", Some("2024-01-01"), Some("2024-01-31"))
255 .await;
256 assert!(result.is_ok());
257 }
258
259 #[tokio::test]
260 #[ignore = "requires FMP API key"]
261 async fn test_various_crypto_quotes() {
262 let client = FmpClient::new().unwrap();
263 for symbol in &["BTCUSD", "ETHUSD", "ADAUSD"] {
264 let result = client.crypto().get_crypto_quote(symbol).await;
265 assert!(result.is_ok());
266 }
267 }
268
269 #[tokio::test]
270 #[ignore = "requires FMP API key"]
271 async fn test_various_intervals() {
272 let client = FmpClient::new().unwrap();
273 for interval in &["1min", "5min", "15min", "1hour"] {
274 let result = client
275 .crypto()
276 .get_crypto_intraday("BTCUSD", interval)
277 .await;
278 assert!(result.is_ok());
279 }
280 }
281
282 #[tokio::test]
284 async fn test_invalid_api_key() {
285 let client = FmpClient::builder()
286 .api_key("invalid_key_12345")
287 .build()
288 .unwrap();
289 let result = client.crypto().get_crypto_list().await;
290 assert!(result.is_err());
291 }
292
293 #[tokio::test]
294 #[ignore = "requires FMP API key"]
295 async fn test_invalid_symbol() {
296 let client = FmpClient::new().unwrap();
297 let result = client.crypto().get_crypto_quote("INVALIDCRYPTO123").await;
298 match result {
300 Ok(data) => assert!(data.is_empty()),
301 Err(_) => {} }
303 }
304}