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, ¶ms, 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}