use crate::adapters::yahoo::client::YahooClient;
use crate::adapters::yahoo::endpoints::api;
use crate::error::Result;
use crate::models::quote::{FormattedValue, Price, QuoteSummaryResponse};
use tracing::info;
#[allow(dead_code)]
pub(crate) async fn fetch(client: &YahooClient, symbols: &[&str]) -> Result<serde_json::Value> {
crate::adapters::yahoo::common::validate_symbols(symbols)?;
info!("Fetching batch quotes for {} symbols", symbols.len());
let params = [("symbols", symbols.join(","))];
let response = client.request_with_params(api::QUOTES, ¶ms).await?;
Ok(response.json().await?)
}
#[allow(dead_code)]
pub(crate) async fn fetch_with_fields(
client: &YahooClient,
symbols: &[&str],
fields: Option<&[&str]>,
formatted: bool,
include_logo: bool,
) -> Result<serde_json::Value> {
crate::adapters::yahoo::common::validate_symbols(symbols)?;
info!(
"Fetching batch quotes for {} symbols with custom fields (formatted={}, include_logo={})",
symbols.len(),
formatted,
include_logo
);
let config = client.config();
let mut params: Vec<(&str, std::borrow::Cow<str>)> = vec![
("symbols", symbols.join(",").into()),
(
"formatted",
if formatted {
"true".into()
} else {
"false".into()
},
),
];
if let Some(field_list) = fields {
params.push(("fields", field_list.join(",").into()));
}
if include_logo {
params.push(("imgHeights", "50".into()));
params.push(("imgWidths", "50".into()));
params.push(("imgLabels", "logoUrl".into()));
}
params.push(("overnightPrice", "true".into()));
params.push(("lang", (&*config.lang).into()));
params.push(("region", (&*config.region).into()));
let response = client.request_with_params(api::QUOTES, ¶ms).await?;
Ok(response.json().await?)
}
pub(crate) async fn fetch_quotes_batch(
client: &YahooClient,
symbols: &[&str],
) -> Result<Vec<(String, QuoteSummaryResponse)>> {
let json = fetch(client, symbols).await?;
let result = json
.get("quoteResponse")
.and_then(|qr| qr.get("result"))
.and_then(|r| r.as_array());
let mut quotes = Vec::new();
if let Some(results) = result {
for item in results {
let symbol = item["symbol"].as_str().unwrap_or("").to_string();
let price = Price {
short_name: item["shortName"].as_str().map(String::from),
long_name: item["longName"].as_str().map(String::from),
exchange_name: item["fullExchangeName"].as_str().map(String::from),
exchange: item["exchange"].as_str().map(String::from),
quote_type: item["quoteType"].as_str().map(String::from),
currency: item["currency"].as_str().map(String::from),
market_state: item["marketState"].as_str().map(String::from),
regular_market_price: item["regularMarketPrice"].as_f64().map(FormattedValue::new),
regular_market_change: item["regularMarketChange"]
.as_f64()
.map(FormattedValue::new),
regular_market_change_percent: item["regularMarketChangePercent"]
.as_f64()
.map(FormattedValue::new),
regular_market_volume: item["regularMarketVolume"]
.as_i64()
.map(FormattedValue::new),
regular_market_previous_close: item["regularMarketPreviousClose"]
.as_f64()
.map(FormattedValue::new),
regular_market_open: item["regularMarketOpen"].as_f64().map(FormattedValue::new),
regular_market_day_high: item["regularMarketDayHigh"]
.as_f64()
.map(FormattedValue::new),
regular_market_day_low: item["regularMarketDayLow"]
.as_f64()
.map(FormattedValue::new),
market_cap: item["marketCap"].as_i64().map(FormattedValue::new),
..Default::default()
};
let response = QuoteSummaryResponse {
symbol: symbol.clone(),
price: Some(price),
..Default::default()
};
quotes.push((symbol, response));
}
}
Ok(quotes)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::adapters::yahoo::client::ClientConfig;
#[tokio::test]
#[ignore] async fn test_fetch_batch_quotes() {
let client = YahooClient::new(ClientConfig::default()).await.unwrap();
let result = fetch(&client, &["AAPL", "GOOGL"]).await;
assert!(result.is_ok());
let json = result.unwrap();
assert!(json.get("quoteResponse").is_some());
}
#[tokio::test]
#[ignore = "requires network access - validation tested in common::tests"]
async fn test_empty_symbols() {
let client = YahooClient::new(ClientConfig::default()).await.unwrap();
let result = fetch(&client, &[]).await;
assert!(result.is_err());
}
}