use crate::adapters::yahoo::client::YahooClient;
use crate::adapters::yahoo::endpoints::api;
use crate::constants::Region;
use crate::error::Result;
use tracing::info;
#[derive(Debug, Clone)]
pub struct SearchOptions {
pub quotes_count: u32,
pub news_count: u32,
pub enable_fuzzy_query: bool,
pub enable_logo_url: bool,
pub enable_research_reports: bool,
pub enable_cultural_assets: bool,
pub recommend_count: u32,
pub region: Option<Region>,
}
impl Default for SearchOptions {
fn default() -> Self {
Self {
quotes_count: 10,
news_count: 0,
enable_fuzzy_query: false,
enable_logo_url: true,
enable_research_reports: false,
enable_cultural_assets: false,
recommend_count: 5,
region: None,
}
}
}
impl SearchOptions {
pub fn new() -> Self {
Self::default()
}
pub fn quotes_count(mut self, count: u32) -> Self {
self.quotes_count = count;
self
}
pub fn news_count(mut self, count: u32) -> Self {
self.news_count = count;
self
}
pub fn enable_fuzzy_query(mut self, enable: bool) -> Self {
self.enable_fuzzy_query = enable;
self
}
pub fn enable_logo_url(mut self, enable: bool) -> Self {
self.enable_logo_url = enable;
self
}
pub fn enable_research_reports(mut self, enable: bool) -> Self {
self.enable_research_reports = enable;
self
}
pub fn enable_cultural_assets(mut self, enable: bool) -> Self {
self.enable_cultural_assets = enable;
self
}
pub fn recommend_count(mut self, count: u32) -> Self {
self.recommend_count = count;
self
}
pub fn region(mut self, region: Region) -> Self {
self.region = Some(region);
self
}
}
pub(crate) async fn fetch_bytes(
client: &YahooClient,
query: &str,
options: &SearchOptions,
) -> Result<impl std::ops::Deref<Target = [u8]>> {
if query.trim().is_empty() {
return Err(crate::error::FinanceError::InvalidParameter {
param: "query".to_string(),
reason: "Empty search query".to_string(),
});
}
info!("Searching for: {} (options: {:?})", query, options);
let quotes_count = options.quotes_count.to_string();
let news_count = options.news_count.to_string();
let fuzzy = options.enable_fuzzy_query.to_string();
let logo = options.enable_logo_url.to_string();
let research = options.enable_research_reports.to_string();
let cultural = options.enable_cultural_assets.to_string();
let recommend = options.recommend_count.to_string();
let lang = options
.region
.as_ref()
.map(|c| c.lang().to_string())
.unwrap_or_else(|| client.config().lang.clone());
let region = options
.region
.as_ref()
.map(|c| c.region().to_string())
.unwrap_or_else(|| client.config().region.clone());
let params = [
("q", query),
("lang", &lang),
("region", ®ion),
("quotesCount", "es_count),
("newsCount", &news_count),
("enableFuzzyQuery", &fuzzy),
("enableLogoUrl", &logo),
("enableResearchReports", &research),
("enableCulturalAssets", &cultural),
("recommendedCount", &recommend),
("listsCount", "0"), ("enableNavLinks", "false"), ("enableEnhancedTrivialQuery", "true"),
];
let response = client.request_with_params(api::SEARCH, ¶ms).await?;
Ok(response.bytes().await?)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::adapters::yahoo::client::ClientConfig;
#[tokio::test]
#[ignore] async fn test_fetch_search() {
let client = YahooClient::new(ClientConfig::default()).await.unwrap();
let options = SearchOptions::new().quotes_count(5);
let bytes = fetch_bytes(&client, "Apple", &options).await.unwrap();
let results: crate::models::discovery::search::SearchResults =
serde_json::from_slice(&bytes).unwrap();
assert!(!results.quotes.0.is_empty());
}
#[tokio::test]
#[ignore] async fn test_fetch_search_with_news() {
let client = YahooClient::new(ClientConfig::default()).await.unwrap();
let options = SearchOptions::new()
.quotes_count(5)
.news_count(3)
.enable_research_reports(true);
let bytes = fetch_bytes(&client, "NVDA", &options).await.unwrap();
let results: crate::models::discovery::search::SearchResults =
serde_json::from_slice(&bytes).unwrap();
assert!(!results.quotes.0.is_empty());
}
#[test]
fn search_results_parse_from_slice() {
use crate::models::discovery::search::SearchResults;
let body = br#"{
"count": 2,
"quotes": [
{"symbol": "AAPL", "shortName": "Apple Inc.", "quoteType": "EQUITY"},
{"symbol": "AAPU"}
],
"news": [],
"totalTime": 42
}"#;
let parsed: SearchResults = serde_json::from_slice(body).unwrap();
assert_eq!(parsed.result_count(), 2);
assert_eq!(parsed.quotes.len(), 2);
assert_eq!(parsed.quotes[0].symbol, "AAPL");
assert_eq!(parsed.quotes[1].short_name, None);
assert!(parsed.news.is_empty());
}
#[tokio::test]
#[ignore = "requires network access - validation tested in common::tests"]
async fn test_empty_query() {
let client = YahooClient::new(ClientConfig::default()).await.unwrap();
let options = SearchOptions::new();
let result = fetch_bytes(&client, "", &options).await;
assert!(result.is_err());
}
}