finance_query/endpoints/
search.rs

1use super::urls::api;
2/// Search endpoint
3///
4/// Search for quotes, news, and research reports on Yahoo Finance.
5use crate::client::YahooClient;
6use crate::constants::Region;
7use crate::error::Result;
8use tracing::info;
9
10/// Search configuration options
11#[derive(Debug, Clone)]
12pub struct SearchOptions {
13    /// Maximum number of quote results (default: 10)
14    pub quotes_count: u32,
15    /// Maximum number of news results (default: 0 = disabled)
16    pub news_count: u32,
17    /// Enable fuzzy matching for typos (default: false)
18    pub enable_fuzzy_query: bool,
19    /// Enable logo URLs in results (default: true)
20    pub enable_logo_url: bool,
21    /// Enable research reports in results (default: false)
22    pub enable_research_reports: bool,
23    /// Enable cultural assets (NFT indices) in results (default: false)
24    pub enable_cultural_assets: bool,
25    /// Recommended count (default: 5)
26    pub recommend_count: u32,
27    /// Region for language/region settings. If None, uses client default.
28    pub region: Option<Region>,
29}
30
31impl Default for SearchOptions {
32    fn default() -> Self {
33        Self {
34            quotes_count: 10,
35            news_count: 0,
36            enable_fuzzy_query: false,
37            enable_logo_url: true,
38            enable_research_reports: false,
39            enable_cultural_assets: false,
40            recommend_count: 5,
41            region: None,
42        }
43    }
44}
45
46impl SearchOptions {
47    /// Create new search options with defaults
48    pub fn new() -> Self {
49        Self::default()
50    }
51
52    /// Set maximum quote results
53    pub fn quotes_count(mut self, count: u32) -> Self {
54        self.quotes_count = count;
55        self
56    }
57
58    /// Set maximum news results
59    pub fn news_count(mut self, count: u32) -> Self {
60        self.news_count = count;
61        self
62    }
63
64    /// Enable or disable fuzzy query matching
65    pub fn enable_fuzzy_query(mut self, enable: bool) -> Self {
66        self.enable_fuzzy_query = enable;
67        self
68    }
69
70    /// Enable or disable logo URLs
71    pub fn enable_logo_url(mut self, enable: bool) -> Self {
72        self.enable_logo_url = enable;
73        self
74    }
75
76    /// Enable or disable research reports
77    pub fn enable_research_reports(mut self, enable: bool) -> Self {
78        self.enable_research_reports = enable;
79        self
80    }
81
82    /// Enable or disable cultural assets (NFT indices)
83    pub fn enable_cultural_assets(mut self, enable: bool) -> Self {
84        self.enable_cultural_assets = enable;
85        self
86    }
87
88    /// Set recommend count
89    pub fn recommend_count(mut self, count: u32) -> Self {
90        self.recommend_count = count;
91        self
92    }
93
94    /// Set region for language settings
95    pub fn region(mut self, region: Region) -> Self {
96        self.region = Some(region);
97        self
98    }
99}
100
101/// Search for quotes, news, and research reports
102///
103/// # Arguments
104///
105/// * `client` - The Yahoo Finance client
106/// * `query` - Search query string
107/// * `options` - Search configuration options
108///
109/// # Example
110///
111/// ```ignore
112/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
113/// # let client = finance_query::YahooClient::new(Default::default()).await?;
114/// use finance_query::endpoints::search::{fetch, SearchOptions};
115/// let options = SearchOptions::new().quotes_count(10).news_count(5);
116/// let results = fetch(&client, "Apple", &options).await?;
117/// # Ok(())
118/// # }
119/// ```
120pub async fn fetch(
121    client: &YahooClient,
122    query: &str,
123    options: &SearchOptions,
124) -> Result<serde_json::Value> {
125    if query.trim().is_empty() {
126        return Err(crate::error::YahooError::InvalidParameter {
127            param: "query".to_string(),
128            reason: "Empty search query".to_string(),
129        });
130    }
131
132    info!("Searching for: {} (options: {:?})", query, options);
133
134    let quotes_count = options.quotes_count.to_string();
135    let news_count = options.news_count.to_string();
136    let fuzzy = options.enable_fuzzy_query.to_string();
137    let logo = options.enable_logo_url.to_string();
138    let research = options.enable_research_reports.to_string();
139    let cultural = options.enable_cultural_assets.to_string();
140    let recommend = options.recommend_count.to_string();
141
142    // Use provided regions's lang/code or fall back to client config
143    let lang = options
144        .region
145        .as_ref()
146        .map(|c| c.lang().to_string())
147        .unwrap_or_else(|| client.config().lang.clone());
148    let region = options
149        .region
150        .as_ref()
151        .map(|c| c.region().to_string())
152        .unwrap_or_else(|| client.config().region.clone());
153
154    let params = [
155        ("q", query),
156        ("lang", &lang),
157        ("region", &region),
158        ("quotesCount", &quotes_count),
159        ("newsCount", &news_count),
160        ("enableFuzzyQuery", &fuzzy),
161        ("enableLogoUrl", &logo),
162        ("enableResearchReports", &research),
163        ("enableCulturalAssets", &cultural),
164        ("recommendedCount", &recommend),
165        ("listsCount", "0"),         // Disable Yahoo-specific lists
166        ("enableNavLinks", "false"), // Disable Yahoo navigation links
167        ("enableEnhancedTrivialQuery", "true"),
168    ];
169
170    let response = client.request_with_params(api::SEARCH, &params).await?;
171
172    Ok(response.json().await?)
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178    use crate::client::ClientConfig;
179
180    #[tokio::test]
181    #[ignore] // Requires network access
182    async fn test_fetch_search() {
183        let client = YahooClient::new(ClientConfig::default()).await.unwrap();
184        let options = SearchOptions::new().quotes_count(5);
185        let result = fetch(&client, "Apple", &options).await;
186        assert!(result.is_ok());
187        let json = result.unwrap();
188        assert!(json.get("quotes").is_some());
189    }
190
191    #[tokio::test]
192    #[ignore] // Requires network access
193    async fn test_fetch_search_with_news() {
194        let client = YahooClient::new(ClientConfig::default()).await.unwrap();
195        let options = SearchOptions::new()
196            .quotes_count(5)
197            .news_count(3)
198            .enable_research_reports(true);
199        let result = fetch(&client, "NVDA", &options).await;
200        assert!(result.is_ok());
201        let json = result.unwrap();
202        assert!(json.get("quotes").is_some());
203    }
204
205    #[tokio::test]
206    #[ignore = "requires network access - validation tested in common::tests"]
207    async fn test_empty_query() {
208        let client = YahooClient::new(ClientConfig::default()).await.unwrap();
209        let options = SearchOptions::new();
210        let result = fetch(&client, "", &options).await;
211        assert!(result.is_err());
212    }
213}