1use alpaca_core::{QueryWriter, pagination::PaginatedRequest};
2
3use crate::Error;
4
5use super::{Location, Sort, TimeFrame};
6
7#[derive(Clone, Debug, Default)]
8pub struct BarsRequest {
9 pub location: Location,
10 pub symbols: Vec<String>,
11 pub timeframe: TimeFrame,
12 pub start: Option<String>,
13 pub end: Option<String>,
14 pub limit: Option<u32>,
15 pub sort: Option<Sort>,
16 pub page_token: Option<String>,
17}
18
19#[derive(Clone, Debug, Default)]
20pub struct QuotesRequest {
21 pub location: Location,
22 pub symbols: Vec<String>,
23 pub start: Option<String>,
24 pub end: Option<String>,
25 pub limit: Option<u32>,
26 pub sort: Option<Sort>,
27 pub page_token: Option<String>,
28}
29
30#[derive(Clone, Debug, Default)]
31pub struct TradesRequest {
32 pub location: Location,
33 pub symbols: Vec<String>,
34 pub start: Option<String>,
35 pub end: Option<String>,
36 pub limit: Option<u32>,
37 pub sort: Option<Sort>,
38 pub page_token: Option<String>,
39}
40
41#[derive(Clone, Debug, Default)]
42pub struct LatestBarsRequest {
43 pub location: Location,
44 pub symbols: Vec<String>,
45}
46
47#[derive(Clone, Debug, Default)]
48pub struct LatestQuotesRequest {
49 pub location: Location,
50 pub symbols: Vec<String>,
51}
52
53#[derive(Clone, Debug, Default)]
54pub struct LatestTradesRequest {
55 pub location: Location,
56 pub symbols: Vec<String>,
57}
58
59#[derive(Clone, Debug, Default)]
60pub struct LatestOrderbooksRequest {
61 pub location: Location,
62 pub symbols: Vec<String>,
63}
64
65#[derive(Clone, Debug, Default)]
66pub struct SnapshotsRequest {
67 pub location: Location,
68 pub symbols: Vec<String>,
69}
70
71impl BarsRequest {
72 pub(crate) fn validate(&self) -> Result<(), Error> {
73 validate_required_symbols(&self.symbols)?;
74 validate_limit(self.limit, 1, 10_000)
75 }
76
77 pub(crate) fn into_query(self) -> Vec<(String, String)> {
78 let mut query = QueryWriter::default();
79 query.push_csv("symbols", normalized_crypto_symbols(&self.symbols));
80 query.push_opt("timeframe", Some(self.timeframe));
81 query.push_opt("start", self.start);
82 query.push_opt("end", self.end);
83 query.push_opt("limit", self.limit);
84 query.push_opt("sort", self.sort);
85 query.push_opt("page_token", self.page_token);
86 query.finish()
87 }
88}
89
90impl QuotesRequest {
91 pub(crate) fn validate(&self) -> Result<(), Error> {
92 validate_required_symbols(&self.symbols)?;
93 validate_limit(self.limit, 1, 10_000)
94 }
95
96 pub(crate) fn into_query(self) -> Vec<(String, String)> {
97 let mut query = QueryWriter::default();
98 query.push_csv("symbols", normalized_crypto_symbols(&self.symbols));
99 query.push_opt("start", self.start);
100 query.push_opt("end", self.end);
101 query.push_opt("limit", self.limit);
102 query.push_opt("sort", self.sort);
103 query.push_opt("page_token", self.page_token);
104 query.finish()
105 }
106}
107
108impl TradesRequest {
109 pub(crate) fn validate(&self) -> Result<(), Error> {
110 validate_required_symbols(&self.symbols)?;
111 validate_limit(self.limit, 1, 10_000)
112 }
113
114 pub(crate) fn into_query(self) -> Vec<(String, String)> {
115 let mut query = QueryWriter::default();
116 query.push_csv("symbols", normalized_crypto_symbols(&self.symbols));
117 query.push_opt("start", self.start);
118 query.push_opt("end", self.end);
119 query.push_opt("limit", self.limit);
120 query.push_opt("sort", self.sort);
121 query.push_opt("page_token", self.page_token);
122 query.finish()
123 }
124}
125
126impl LatestBarsRequest {
127 pub(crate) fn validate(&self) -> Result<(), Error> {
128 validate_required_symbols(&self.symbols)
129 }
130
131 pub(crate) fn into_query(self) -> Vec<(String, String)> {
132 latest_batch_query(self.symbols)
133 }
134}
135
136impl LatestQuotesRequest {
137 pub(crate) fn validate(&self) -> Result<(), Error> {
138 validate_required_symbols(&self.symbols)
139 }
140
141 pub(crate) fn into_query(self) -> Vec<(String, String)> {
142 latest_batch_query(self.symbols)
143 }
144}
145
146impl LatestTradesRequest {
147 pub(crate) fn validate(&self) -> Result<(), Error> {
148 validate_required_symbols(&self.symbols)
149 }
150
151 pub(crate) fn into_query(self) -> Vec<(String, String)> {
152 latest_batch_query(self.symbols)
153 }
154}
155
156impl LatestOrderbooksRequest {
157 pub(crate) fn validate(&self) -> Result<(), Error> {
158 validate_required_symbols(&self.symbols)
159 }
160
161 pub(crate) fn into_query(self) -> Vec<(String, String)> {
162 latest_batch_query(self.symbols)
163 }
164}
165
166impl SnapshotsRequest {
167 pub(crate) fn validate(&self) -> Result<(), Error> {
168 validate_required_symbols(&self.symbols)
169 }
170
171 pub(crate) fn into_query(self) -> Vec<(String, String)> {
172 latest_batch_query(self.symbols)
173 }
174}
175
176impl PaginatedRequest for BarsRequest {
177 fn with_page_token(&self, page_token: Option<String>) -> Self {
178 let mut next = self.clone();
179 next.page_token = page_token;
180 next
181 }
182}
183
184impl PaginatedRequest for QuotesRequest {
185 fn with_page_token(&self, page_token: Option<String>) -> Self {
186 let mut next = self.clone();
187 next.page_token = page_token;
188 next
189 }
190}
191
192impl PaginatedRequest for TradesRequest {
193 fn with_page_token(&self, page_token: Option<String>) -> Self {
194 let mut next = self.clone();
195 next.page_token = page_token;
196 next
197 }
198}
199
200fn latest_batch_query(symbols: Vec<String>) -> Vec<(String, String)> {
201 let mut query = QueryWriter::default();
202 query.push_csv("symbols", normalized_crypto_symbols(&symbols));
203 query.finish()
204}
205
206fn validate_required_symbols(symbols: &[String]) -> Result<(), Error> {
207 if symbols.is_empty() {
208 return Err(Error::InvalidRequest(
209 "symbols are invalid: must not be empty".to_owned(),
210 ));
211 }
212
213 if symbols
214 .iter()
215 .any(|symbol| normalized_crypto_symbol(symbol).is_empty())
216 {
217 return Err(Error::InvalidRequest(
218 "symbols are invalid: must not contain empty or whitespace-only entries".to_owned(),
219 ));
220 }
221
222 Ok(())
223}
224
225fn normalized_crypto_symbol(symbol: &str) -> String {
226 symbol.trim().to_owned()
227}
228
229fn normalized_crypto_symbols(symbols: &[String]) -> Vec<String> {
230 symbols
231 .iter()
232 .map(|symbol| normalized_crypto_symbol(symbol))
233 .collect()
234}
235
236fn validate_limit(limit: Option<u32>, min: u32, max: u32) -> Result<(), Error> {
237 if let Some(limit) = limit
238 && !(min..=max).contains(&limit)
239 {
240 return Err(Error::InvalidRequest(format!(
241 "limit must be between {min} and {max}"
242 )));
243 }
244
245 Ok(())
246}