Skip to main content

finance_query/models/discovery/lookup/
response.rs

1//! Lookup Response Model
2//!
3//! Top-level wrapper for symbol lookup results
4
5use super::LookupQuote;
6use serde::{Deserialize, Serialize};
7
8/// Raw response wrapper from Yahoo Finance lookup endpoint
9#[derive(Debug, Clone, Deserialize)]
10#[serde(rename_all = "camelCase")]
11struct RawLookupResponse {
12    finance: Option<RawFinanceResult>,
13}
14
15#[derive(Debug, Clone, Deserialize)]
16#[serde(rename_all = "camelCase")]
17struct RawFinanceResult {
18    result: Option<Vec<RawLookupResult>>,
19    #[allow(dead_code)] // serde completeness field; never read
20    error: Option<serde_json::Value>,
21}
22
23#[derive(Debug, Clone, Deserialize)]
24#[serde(rename_all = "camelCase")]
25struct RawLookupResult {
26    documents: Option<Vec<LookupQuote>>,
27    start: Option<i32>,
28    count: Option<i32>,
29}
30
31/// Response wrapper for lookup endpoint
32#[derive(Debug, Clone, Serialize, Deserialize)]
33#[non_exhaustive]
34#[serde(rename_all = "camelCase")]
35pub struct LookupResults {
36    /// Quote/document results
37    #[serde(default)]
38    pub quotes: Vec<LookupQuote>,
39    /// Starting index
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub start: Option<i32>,
42    /// Total result count
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub count: Option<i32>,
45}
46
47impl LookupResults {
48    /// Parse LookupResults from raw JSON value
49    ///
50    /// # Example
51    /// ```no_run
52    /// # use finance_query::LookupResults;
53    /// let json = serde_json::json!({
54    ///     "finance": {
55    ///         "result": [{
56    ///             "documents": [],
57    ///             "start": 0,
58    ///             "count": 0
59    ///         }]
60    ///     }
61    /// });
62    /// let results = LookupResults::from_json(json)?;
63    /// # Ok::<(), serde_json::Error>(())
64    /// ```
65    pub fn from_json(value: serde_json::Value) -> Result<Self, serde_json::Error> {
66        Ok(Self::from_raw(serde_json::from_value(value)?))
67    }
68
69    /// Same reshape as [`from_json`](Self::from_json), straight from the raw
70    /// response body — no `serde_json::Value` tree in between.
71    pub(crate) fn from_slice(bytes: &[u8]) -> Result<Self, serde_json::Error> {
72        Ok(Self::from_raw(serde_json::from_slice(bytes)?))
73    }
74
75    fn from_raw(raw: RawLookupResponse) -> Self {
76        let (quotes, start, count) = raw
77            .finance
78            .and_then(|f| f.result)
79            .and_then(|r| r.into_iter().next())
80            .map(|result| {
81                (
82                    result.documents.unwrap_or_default(),
83                    result.start,
84                    result.count,
85                )
86            })
87            .unwrap_or_default();
88
89        LookupResults {
90            quotes,
91            start,
92            count,
93        }
94    }
95
96    /// Get all quote results
97    pub fn quotes(&self) -> &[LookupQuote] {
98        &self.quotes
99    }
100
101    /// Get total result count
102    pub fn result_count(&self) -> i32 {
103        self.count.unwrap_or(0)
104    }
105
106    /// Check if any results were found
107    pub fn is_empty(&self) -> bool {
108        self.quotes.is_empty()
109    }
110}
111
112#[cfg(feature = "dataframe")]
113impl LookupResults {
114    /// Converts the quotes to a polars DataFrame.
115    pub fn to_dataframe(&self) -> ::polars::prelude::PolarsResult<::polars::prelude::DataFrame> {
116        LookupQuote::vec_to_dataframe(&self.quotes)
117    }
118}