Skip to main content

rustyfinance/
lookup.rs

1use serde_json::Value;
2
3use crate::consts::QUERY1_URL;
4use crate::data::YfData;
5use crate::errors::{Result, YfError};
6
7#[derive(Debug, Clone, Copy)]
8pub enum LookupType {
9    All,
10    Equity,
11    MutualFund,
12    Etf,
13    Index,
14    Future,
15    Currency,
16    Cryptocurrency,
17}
18
19impl LookupType {
20    pub fn as_str(self) -> &'static str {
21        match self {
22            Self::All => "all",
23            Self::Equity => "equity",
24            Self::MutualFund => "mutualfund",
25            Self::Etf => "etf",
26            Self::Index => "index",
27            Self::Future => "future",
28            Self::Currency => "currency",
29            Self::Cryptocurrency => "cryptocurrency",
30        }
31    }
32}
33
34#[derive(Debug, Clone)]
35pub struct Lookup {
36    pub query: String,
37    pub timeout: u64,
38    data: YfData,
39}
40
41impl Lookup {
42    pub fn new(query: impl Into<String>) -> Self {
43        Self {
44            query: query.into(),
45            timeout: 30,
46            data: YfData::new(),
47        }
48    }
49
50    pub fn fetch(&self, lookup_type: LookupType, count: usize) -> Result<Value> {
51        let url = format!("{}/v1/finance/lookup", &*QUERY1_URL);
52        let params = vec![
53            ("query", self.query.clone()),
54            ("type", lookup_type.as_str().to_string()),
55            ("start", "0".to_string()),
56            ("count", count.to_string()),
57            ("formatted", "false".to_string()),
58            ("fetchPricingData", "true".to_string()),
59            ("lang", "en-US".to_string()),
60            ("region", "US".to_string()),
61        ];
62        let json = self.data.get_json(&url, &params, self.timeout)?;
63        if json
64            .get("finance")
65            .and_then(|v| v.get("error"))
66            .and_then(Value::as_object)
67            .is_some_and(|obj| !obj.is_empty())
68        {
69            return Err(YfError::data(format!(
70                "{}: 'lookup' fetch returned error",
71                self.query
72            )));
73        }
74        Ok(json)
75    }
76
77    pub fn documents(&self, lookup_type: LookupType, count: usize) -> Result<Vec<Value>> {
78        let json = self.fetch(lookup_type, count)?;
79        Ok(parse_lookup_documents(&json))
80    }
81
82    pub fn fetch_default(&self) -> Result<Vec<Value>> {
83        self.documents(LookupType::All, 25)
84    }
85}
86
87pub(crate) fn parse_lookup_documents(json: &Value) -> Vec<Value> {
88    json.get("finance")
89        .and_then(|v| v.get("result"))
90        .and_then(Value::as_array)
91        .and_then(|arr| arr.first())
92        .and_then(|v| v.get("documents"))
93        .and_then(Value::as_array)
94        .cloned()
95        .unwrap_or_default()
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use serde_json::json;
102
103    #[test]
104    fn parse_lookup_documents_handles_normal_payload() {
105        let payload = json!({
106            "finance": {
107                "result": [{
108                    "documents": [
109                        {"symbol": "AAPL", "name": "Apple"},
110                        {"symbol": "MSFT", "name": "Microsoft"}
111                    ]
112                }]
113            }
114        });
115        let docs = parse_lookup_documents(&payload);
116        assert_eq!(docs.len(), 2);
117        assert_eq!(docs[0].get("symbol").and_then(Value::as_str), Some("AAPL"));
118    }
119
120    #[test]
121    fn parse_lookup_documents_handles_missing_payload() {
122        let payload = json!({"finance": {"result": []}});
123        let docs = parse_lookup_documents(&payload);
124        assert!(docs.is_empty());
125    }
126}