1use std::collections::HashMap;
2
3use alpaca_core::{Error, pagination::PaginatedResponse};
4use serde::{Deserialize, Serialize};
5
6use super::{Bar, Currency, DailyAuction, Quote, Snapshot, Trade};
7
8#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
9pub struct BarsResponse {
10 #[serde(default)]
11 pub bars: HashMap<String, Vec<Bar>>,
12 pub next_page_token: Option<String>,
13 pub currency: Option<Currency>,
14}
15
16#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
17pub struct AuctionsResponse {
18 #[serde(default)]
19 pub auctions: HashMap<String, Vec<DailyAuction>>,
20 pub next_page_token: Option<String>,
21 pub currency: Option<Currency>,
22}
23
24#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
25pub struct QuotesResponse {
26 #[serde(default)]
27 pub quotes: HashMap<String, Vec<Quote>>,
28 pub next_page_token: Option<String>,
29 pub currency: Option<Currency>,
30}
31
32#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
33pub struct TradesResponse {
34 #[serde(default)]
35 pub trades: HashMap<String, Vec<Trade>>,
36 pub next_page_token: Option<String>,
37 pub currency: Option<Currency>,
38}
39
40#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
41pub struct LatestBarsResponse {
42 #[serde(default)]
43 pub bars: HashMap<String, Bar>,
44 pub currency: Option<Currency>,
45}
46
47#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
48pub struct LatestQuotesResponse {
49 #[serde(default)]
50 pub quotes: HashMap<String, Quote>,
51 pub currency: Option<Currency>,
52}
53
54#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
55pub struct LatestTradesResponse {
56 #[serde(default)]
57 pub trades: HashMap<String, Trade>,
58 pub currency: Option<Currency>,
59}
60
61pub type SnapshotsResponse = HashMap<String, Snapshot>;
62
63pub type ConditionCodesResponse = HashMap<String, String>;
64pub type ExchangeCodesResponse = HashMap<String, String>;
65
66impl PaginatedResponse for BarsResponse {
67 fn next_page_token(&self) -> Option<&str> {
68 self.next_page_token.as_deref()
69 }
70
71 fn merge_page(&mut self, next: Self) -> Result<(), Error> {
72 merge_batch_currency("stocks.bars_all", &mut self.currency, next.currency)?;
73 merge_batch_page(&mut self.bars, next.bars);
74 self.next_page_token = next.next_page_token;
75 Ok(())
76 }
77
78 fn clear_next_page_token(&mut self) {
79 self.next_page_token = None;
80 }
81}
82
83impl PaginatedResponse for AuctionsResponse {
84 fn next_page_token(&self) -> Option<&str> {
85 self.next_page_token.as_deref()
86 }
87
88 fn merge_page(&mut self, next: Self) -> Result<(), Error> {
89 merge_batch_currency("stocks.auctions_all", &mut self.currency, next.currency)?;
90 merge_batch_page(&mut self.auctions, next.auctions);
91 self.next_page_token = next.next_page_token;
92 Ok(())
93 }
94
95 fn clear_next_page_token(&mut self) {
96 self.next_page_token = None;
97 }
98}
99
100impl PaginatedResponse for QuotesResponse {
101 fn next_page_token(&self) -> Option<&str> {
102 self.next_page_token.as_deref()
103 }
104
105 fn merge_page(&mut self, next: Self) -> Result<(), Error> {
106 merge_batch_currency("stocks.quotes_all", &mut self.currency, next.currency)?;
107 merge_batch_page(&mut self.quotes, next.quotes);
108 self.next_page_token = next.next_page_token;
109 Ok(())
110 }
111
112 fn clear_next_page_token(&mut self) {
113 self.next_page_token = None;
114 }
115}
116
117impl PaginatedResponse for TradesResponse {
118 fn next_page_token(&self) -> Option<&str> {
119 self.next_page_token.as_deref()
120 }
121
122 fn merge_page(&mut self, next: Self) -> Result<(), Error> {
123 merge_batch_currency("stocks.trades_all", &mut self.currency, next.currency)?;
124 merge_batch_page(&mut self.trades, next.trades);
125 self.next_page_token = next.next_page_token;
126 Ok(())
127 }
128
129 fn clear_next_page_token(&mut self) {
130 self.next_page_token = None;
131 }
132}
133
134fn merge_batch_currency(
135 operation: &str,
136 currency: &mut Option<Currency>,
137 next_currency: Option<Currency>,
138) -> Result<(), Error> {
139 match (currency.as_ref(), next_currency) {
140 (Some(current), Some(next)) if current != &next => Err(Error::InvalidRequest(format!(
141 "{operation} received mismatched currency across pages: expected {}, got {}",
142 current.as_str(),
143 next.as_str()
144 ))),
145 (None, Some(next)) => {
146 *currency = Some(next);
147 Ok(())
148 }
149 _ => Ok(()),
150 }
151}
152
153fn merge_batch_page<Item>(
154 current: &mut HashMap<String, Vec<Item>>,
155 next: HashMap<String, Vec<Item>>,
156) {
157 for (symbol, mut items) in next {
158 current.entry(symbol).or_default().append(&mut items);
159 }
160}