#![allow(dead_code)]
use serde::{Deserialize, Serialize};
use crate::adapters::common::encode_path_segment;
use crate::error::Result;
use crate::adapters::fmp::build_client;
use crate::adapters::fmp::models::{FmpQuoteDTO, HistoricalPriceResponseDTO, IntradayPriceDTO};
fn crypto_quote_to_canonical(
id: &str,
_vs_currency: &str,
quotes: &[FmpQuoteDTO],
) -> crate::models::crypto::CryptoQuote {
let q = quotes.first();
crate::models::crypto::CryptoQuote {
id: id.to_string(),
symbol: q
.map(|q| q.symbol.clone())
.unwrap_or_else(|| id.to_uppercase()),
name: q.and_then(|q| q.name.clone()).unwrap_or_default(),
price: q.and_then(|q| q.price),
market_cap: q.and_then(|q| q.market_cap),
volume_24h: q.and_then(|q| q.volume),
change_24h: q.and_then(|q| q.change),
change_percent_24h: q.and_then(|q| q.changes_percentage),
high_24h: q.and_then(|q| q.day_high),
low_24h: q.and_then(|q| q.day_low),
circulating_supply: None,
}
}
pub async fn fetch_canonical_crypto_quote(
id: &str,
vs_currency: &str,
) -> Result<crate::models::crypto::CryptoQuote> {
let pair = format!("{}{}", id.to_uppercase(), vs_currency.to_uppercase());
let quotes = crypto_quote(&pair).await?;
Ok(crypto_quote_to_canonical(id, vs_currency, "es))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct AvailableSymbolDTO {
pub symbol: Option<String>,
pub name: Option<String>,
pub currency: Option<String>,
#[serde(rename = "stockExchange")]
pub stock_exchange: Option<String>,
#[serde(rename = "exchangeShortName")]
pub exchange_short_name: Option<String>,
}
pub async fn crypto_quote(symbol: &str) -> Result<Vec<FmpQuoteDTO>> {
let client = build_client()?;
let path = format!("/api/v3/quote/{}", encode_path_segment(symbol));
client.get(&path, &[]).await
}
pub async fn crypto_available() -> Result<Vec<AvailableSymbolDTO>> {
let client = build_client()?;
client
.get("/api/v3/symbol/available-cryptocurrencies", &[])
.await
}
pub async fn crypto_historical(
symbol: &str,
params: &[(&str, &str)],
) -> Result<HistoricalPriceResponseDTO> {
let client = build_client()?;
let path = format!(
"/api/v3/historical-price-full/{}",
encode_path_segment(symbol)
);
client.get(&path, params).await
}
pub async fn crypto_intraday(
symbol: &str,
interval: &str,
params: &[(&str, &str)],
) -> Result<Vec<IntradayPriceDTO>> {
let client = build_client()?;
let path = format!(
"/api/v3/historical-chart/{}/{}",
encode_path_segment(interval),
encode_path_segment(symbol)
);
client.get(&path, params).await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_crypto_available_mock() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/api/v3/symbol/available-cryptocurrencies")
.match_query(mockito::Matcher::AllOf(vec![mockito::Matcher::UrlEncoded(
"apikey".into(),
"test-key".into(),
)]))
.with_status(200)
.with_body(
serde_json::json!([
{
"symbol": "BTCUSD",
"name": "Bitcoin USD",
"currency": "USD",
"stockExchange": "CCC",
"exchangeShortName": "CRYPTO"
}
])
.to_string(),
)
.create_async()
.await;
let client = crate::adapters::fmp::build_test_client(&server.url()).unwrap();
let result: Vec<AvailableSymbolDTO> = client
.get("/api/v3/symbol/available-cryptocurrencies", &[])
.await
.unwrap();
assert_eq!(result.len(), 1);
assert_eq!(result[0].symbol.as_deref(), Some("BTCUSD"));
}
}