finance_query/models/quote/
response.rs1use crate::error::{FinanceError, Result};
6use crate::models::quote::*;
7use serde_json::Value;
8
9#[derive(Debug, Clone, Default)]
19#[non_exhaustive]
20pub struct QuoteSummaryResponse {
21 pub symbol: String,
23
24 pub price: Option<Price>,
26 pub summary_detail: Option<SummaryDetail>,
28 pub financial_data: Option<FinancialData>,
30 pub default_key_statistics: Option<DefaultKeyStatistics>,
32 pub asset_profile: Option<AssetProfile>,
34 pub calendar_events: Option<CalendarEvents>,
36 pub earnings: Option<Earnings>,
38 pub earnings_trend: Option<EarningsTrend>,
40 pub earnings_history: Option<EarningsHistory>,
42 pub recommendation_trend: Option<RecommendationTrend>,
44 pub insider_holders: Option<InsiderHolders>,
46 pub insider_transactions: Option<InsiderTransactions>,
48 pub institution_ownership: Option<InstitutionOwnership>,
50 pub fund_ownership: Option<FundOwnership>,
52 pub major_holders_breakdown: Option<MajorHoldersBreakdown>,
54 pub net_share_purchase_activity: Option<NetSharePurchaseActivity>,
56 pub quote_type: Option<QuoteTypeData>,
58 pub summary_profile: Option<SummaryProfile>,
60 pub sec_filings: Option<SecFilings>,
62 pub upgrade_downgrade_history: Option<UpgradeDowngradeHistory>,
64 pub fund_performance: Option<FundPerformance>,
66 pub fund_profile: Option<FundProfile>,
68 pub top_holdings: Option<TopHoldings>,
70 pub index_trend: Option<IndexTrend>,
72 pub industry_trend: Option<IndustryTrend>,
74 pub sector_trend: Option<SectorTrend>,
76 pub equity_performance: Option<EquityPerformance>,
78}
79
80impl QuoteSummaryResponse {
81 pub(crate) fn from_json(json: Value, symbol: &str) -> Result<Self> {
95 let quote_summary =
110 json.get("quoteSummary")
111 .ok_or_else(|| FinanceError::ResponseStructureError {
112 field: "quoteSummary".to_string(),
113 context: "Missing quoteSummary field".to_string(),
114 })?;
115
116 if let Some(error) = quote_summary.get("error")
118 && !error.is_null()
119 {
120 return Err(FinanceError::ApiError(format!("API error: {}", error)));
121 }
122
123 let result = quote_summary
124 .get("result")
125 .and_then(|r| r.as_array())
126 .ok_or_else(|| FinanceError::ResponseStructureError {
127 field: "result".to_string(),
128 context: "Missing or invalid result field".to_string(),
129 })?;
130
131 if result.is_empty() {
132 return Err(FinanceError::ApiError(format!(
133 "No data found for symbol: {}",
134 symbol
135 )));
136 }
137
138 let data = &result[0];
139
140 macro_rules! deserialize_module {
142 ($name:expr) => {
143 data.get($name)
144 .and_then(|v| serde_json::from_value(v.clone()).ok())
145 };
146 }
147
148 Ok(Self {
149 symbol: symbol.to_string(),
150 price: deserialize_module!("price"),
151 summary_detail: deserialize_module!("summaryDetail"),
152 financial_data: deserialize_module!("financialData"),
153 default_key_statistics: deserialize_module!("defaultKeyStatistics"),
154 asset_profile: deserialize_module!("assetProfile"),
155 calendar_events: deserialize_module!("calendarEvents"),
156 earnings: deserialize_module!("earnings"),
157 earnings_trend: deserialize_module!("earningsTrend"),
158 earnings_history: deserialize_module!("earningsHistory"),
159 recommendation_trend: deserialize_module!("recommendationTrend"),
160 insider_holders: deserialize_module!("insiderHolders"),
161 insider_transactions: deserialize_module!("insiderTransactions"),
162 institution_ownership: deserialize_module!("institutionOwnership"),
163 fund_ownership: deserialize_module!("fundOwnership"),
164 major_holders_breakdown: deserialize_module!("majorHoldersBreakdown"),
165 net_share_purchase_activity: deserialize_module!("netSharePurchaseActivity"),
166 quote_type: deserialize_module!("quoteType"),
167 summary_profile: deserialize_module!("summaryProfile"),
168 sec_filings: deserialize_module!("secFilings"),
169 upgrade_downgrade_history: deserialize_module!("upgradeDowngradeHistory"),
170 fund_performance: deserialize_module!("fundPerformance"),
171 fund_profile: deserialize_module!("fundProfile"),
172 top_holdings: deserialize_module!("topHoldings"),
173 index_trend: deserialize_module!("indexTrend"),
174 industry_trend: deserialize_module!("industryTrend"),
175 sector_trend: deserialize_module!("sectorTrend"),
176 equity_performance: deserialize_module!("equityPerformance"),
177 })
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use serde_json::json;
185
186 #[test]
187 fn test_from_json_valid() {
188 let json = json!({
189 "quoteSummary": {
190 "result": [
191 {
192 "price": {
193 "regularMarketPrice": {
194 "raw": 150.0,
195 "fmt": "150.00"
196 }
197 },
198 "summaryDetail": {
199 "previousClose": {
200 "raw": 149.0,
201 "fmt": "149.00"
202 }
203 }
204 }
205 ],
206 "error": null
207 }
208 });
209
210 let response = QuoteSummaryResponse::from_json(json, "AAPL").unwrap();
211 assert!(response.price.is_some());
212 assert!(response.summary_detail.is_some());
213 }
214
215 #[test]
216 fn test_from_json_error() {
217 let json = json!({
218 "quoteSummary": {
219 "result": [],
220 "error": null
221 }
222 });
223
224 let response = QuoteSummaryResponse::from_json(json, "INVALID");
225 assert!(response.is_err());
226 }
227}