1use alpaca_core::{QueryWriter, pagination::PaginatedRequest};
2
3use crate::Error;
4
5use super::{Adjustment, AuctionFeed, Currency, DataFeed, Sort, Tape, TickType, TimeFrame};
6
7#[derive(Clone, Debug, Default)]
8pub struct BarsRequest {
9 pub symbols: Vec<String>,
10 pub timeframe: TimeFrame,
11 pub start: Option<String>,
12 pub end: Option<String>,
13 pub limit: Option<u32>,
14 pub adjustment: Option<Adjustment>,
15 pub feed: Option<DataFeed>,
16 pub sort: Option<Sort>,
17 pub asof: Option<String>,
18 pub currency: Option<Currency>,
19 pub page_token: Option<String>,
20}
21
22#[derive(Clone, Debug, Default)]
23pub struct BarsSingleRequest {
24 pub symbol: String,
25 pub timeframe: TimeFrame,
26 pub start: Option<String>,
27 pub end: Option<String>,
28 pub limit: Option<u32>,
29 pub adjustment: Option<Adjustment>,
30 pub feed: Option<DataFeed>,
31 pub sort: Option<Sort>,
32 pub asof: Option<String>,
33 pub currency: Option<Currency>,
34 pub page_token: Option<String>,
35}
36
37#[derive(Clone, Debug, Default)]
38pub struct AuctionsRequest {
39 pub symbols: Vec<String>,
40 pub start: Option<String>,
41 pub end: Option<String>,
42 pub limit: Option<u32>,
43 pub asof: Option<String>,
44 pub feed: Option<AuctionFeed>,
45 pub currency: Option<Currency>,
46 pub page_token: Option<String>,
47 pub sort: Option<Sort>,
48}
49
50#[derive(Clone, Debug, Default)]
51pub struct AuctionsSingleRequest {
52 pub symbol: String,
53 pub start: Option<String>,
54 pub end: Option<String>,
55 pub limit: Option<u32>,
56 pub asof: Option<String>,
57 pub feed: Option<AuctionFeed>,
58 pub currency: Option<Currency>,
59 pub page_token: Option<String>,
60 pub sort: Option<Sort>,
61}
62
63#[derive(Clone, Debug, Default)]
64pub struct QuotesRequest {
65 pub symbols: Vec<String>,
66 pub start: Option<String>,
67 pub end: Option<String>,
68 pub limit: Option<u32>,
69 pub feed: Option<DataFeed>,
70 pub sort: Option<Sort>,
71 pub asof: Option<String>,
72 pub currency: Option<Currency>,
73 pub page_token: Option<String>,
74}
75
76#[derive(Clone, Debug, Default)]
77pub struct QuotesSingleRequest {
78 pub symbol: String,
79 pub start: Option<String>,
80 pub end: Option<String>,
81 pub limit: Option<u32>,
82 pub feed: Option<DataFeed>,
83 pub sort: Option<Sort>,
84 pub asof: Option<String>,
85 pub currency: Option<Currency>,
86 pub page_token: Option<String>,
87}
88
89#[derive(Clone, Debug, Default)]
90pub struct TradesRequest {
91 pub symbols: Vec<String>,
92 pub start: Option<String>,
93 pub end: Option<String>,
94 pub limit: Option<u32>,
95 pub feed: Option<DataFeed>,
96 pub sort: Option<Sort>,
97 pub asof: Option<String>,
98 pub currency: Option<Currency>,
99 pub page_token: Option<String>,
100}
101
102#[derive(Clone, Debug, Default)]
103pub struct TradesSingleRequest {
104 pub symbol: String,
105 pub start: Option<String>,
106 pub end: Option<String>,
107 pub limit: Option<u32>,
108 pub feed: Option<DataFeed>,
109 pub sort: Option<Sort>,
110 pub asof: Option<String>,
111 pub currency: Option<Currency>,
112 pub page_token: Option<String>,
113}
114
115#[derive(Clone, Debug, Default)]
116pub struct LatestBarsRequest {
117 pub symbols: Vec<String>,
118 pub feed: Option<DataFeed>,
119 pub currency: Option<Currency>,
120}
121
122#[derive(Clone, Debug, Default)]
123pub struct LatestBarRequest {
124 pub symbol: String,
125 pub feed: Option<DataFeed>,
126 pub currency: Option<Currency>,
127}
128
129#[derive(Clone, Debug, Default)]
130pub struct LatestQuotesRequest {
131 pub symbols: Vec<String>,
132 pub feed: Option<DataFeed>,
133 pub currency: Option<Currency>,
134}
135
136#[derive(Clone, Debug, Default)]
137pub struct LatestQuoteRequest {
138 pub symbol: String,
139 pub feed: Option<DataFeed>,
140 pub currency: Option<Currency>,
141}
142
143#[derive(Clone, Debug, Default)]
144pub struct LatestTradesRequest {
145 pub symbols: Vec<String>,
146 pub feed: Option<DataFeed>,
147 pub currency: Option<Currency>,
148}
149
150#[derive(Clone, Debug, Default)]
151pub struct LatestTradeRequest {
152 pub symbol: String,
153 pub feed: Option<DataFeed>,
154 pub currency: Option<Currency>,
155}
156
157#[derive(Clone, Debug, Default)]
158pub struct SnapshotsRequest {
159 pub symbols: Vec<String>,
160 pub feed: Option<DataFeed>,
161 pub currency: Option<Currency>,
162}
163
164#[derive(Clone, Debug, Default)]
165pub struct SnapshotRequest {
166 pub symbol: String,
167 pub feed: Option<DataFeed>,
168 pub currency: Option<Currency>,
169}
170
171#[derive(Clone, Debug, Default)]
172pub struct ConditionCodesRequest {
173 pub ticktype: TickType,
174 pub tape: Tape,
175}
176
177impl BarsRequest {
178 pub(crate) fn validate(&self) -> Result<(), Error> {
179 validate_required_symbols(&self.symbols)?;
180 validate_limit(self.limit, 1, 10_000)
181 }
182
183 pub(crate) fn into_query(self) -> Vec<(String, String)> {
184 let mut query = QueryWriter::default();
185 query.push_csv("symbols", self.symbols);
186 query.push_opt("timeframe", Some(self.timeframe));
187 query.push_opt("start", self.start);
188 query.push_opt("end", self.end);
189 query.push_opt("limit", self.limit);
190 query.push_opt("adjustment", self.adjustment);
191 query.push_opt("feed", self.feed);
192 query.push_opt("sort", self.sort);
193 query.push_opt("asof", self.asof);
194 query.push_opt("currency", self.currency);
195 query.push_opt("page_token", self.page_token);
196 query.finish()
197 }
198}
199
200impl BarsSingleRequest {
201 pub(crate) fn validate(&self) -> Result<(), Error> {
202 validate_required_symbol(&self.symbol, "symbol")?;
203 validate_limit(self.limit, 1, 10_000)
204 }
205
206 pub(crate) fn into_query(self) -> Vec<(String, String)> {
207 let mut query = QueryWriter::default();
208 query.push_opt("timeframe", Some(self.timeframe));
209 query.push_opt("start", self.start);
210 query.push_opt("end", self.end);
211 query.push_opt("limit", self.limit);
212 query.push_opt("adjustment", self.adjustment);
213 query.push_opt("feed", self.feed);
214 query.push_opt("sort", self.sort);
215 query.push_opt("asof", self.asof);
216 query.push_opt("currency", self.currency);
217 query.push_opt("page_token", self.page_token);
218 query.finish()
219 }
220}
221
222impl AuctionsRequest {
223 pub(crate) fn validate(&self) -> Result<(), Error> {
224 validate_required_symbols(&self.symbols)?;
225 validate_limit(self.limit, 1, 10_000)
226 }
227
228 pub(crate) fn into_query(self) -> Vec<(String, String)> {
229 let mut query = QueryWriter::default();
230 query.push_csv("symbols", self.symbols);
231 query.push_opt("start", self.start);
232 query.push_opt("end", self.end);
233 query.push_opt("limit", self.limit);
234 query.push_opt("asof", self.asof);
235 query.push_opt("feed", self.feed);
236 query.push_opt("currency", self.currency);
237 query.push_opt("page_token", self.page_token);
238 query.push_opt("sort", self.sort);
239 query.finish()
240 }
241}
242
243impl AuctionsSingleRequest {
244 pub(crate) fn validate(&self) -> Result<(), Error> {
245 validate_required_symbol(&self.symbol, "symbol")?;
246 validate_limit(self.limit, 1, 10_000)
247 }
248
249 pub(crate) fn into_query(self) -> Vec<(String, String)> {
250 let mut query = QueryWriter::default();
251 query.push_opt("start", self.start);
252 query.push_opt("end", self.end);
253 query.push_opt("limit", self.limit);
254 query.push_opt("asof", self.asof);
255 query.push_opt("feed", self.feed);
256 query.push_opt("currency", self.currency);
257 query.push_opt("page_token", self.page_token);
258 query.push_opt("sort", self.sort);
259 query.finish()
260 }
261}
262
263impl QuotesRequest {
264 pub(crate) fn validate(&self) -> Result<(), Error> {
265 validate_required_symbols(&self.symbols)?;
266 validate_limit(self.limit, 1, 10_000)
267 }
268
269 pub(crate) fn into_query(self) -> Vec<(String, String)> {
270 let mut query = QueryWriter::default();
271 query.push_csv("symbols", self.symbols);
272 query.push_opt("start", self.start);
273 query.push_opt("end", self.end);
274 query.push_opt("limit", self.limit);
275 query.push_opt("feed", self.feed);
276 query.push_opt("sort", self.sort);
277 query.push_opt("asof", self.asof);
278 query.push_opt("currency", self.currency);
279 query.push_opt("page_token", self.page_token);
280 query.finish()
281 }
282}
283
284impl QuotesSingleRequest {
285 pub(crate) fn validate(&self) -> Result<(), Error> {
286 validate_required_symbol(&self.symbol, "symbol")?;
287 validate_limit(self.limit, 1, 10_000)
288 }
289
290 pub(crate) fn into_query(self) -> Vec<(String, String)> {
291 let mut query = QueryWriter::default();
292 query.push_opt("start", self.start);
293 query.push_opt("end", self.end);
294 query.push_opt("limit", self.limit);
295 query.push_opt("feed", self.feed);
296 query.push_opt("sort", self.sort);
297 query.push_opt("asof", self.asof);
298 query.push_opt("currency", self.currency);
299 query.push_opt("page_token", self.page_token);
300 query.finish()
301 }
302}
303
304impl TradesRequest {
305 pub(crate) fn validate(&self) -> Result<(), Error> {
306 validate_required_symbols(&self.symbols)?;
307 validate_limit(self.limit, 1, 10_000)
308 }
309
310 pub(crate) fn into_query(self) -> Vec<(String, String)> {
311 let mut query = QueryWriter::default();
312 query.push_csv("symbols", self.symbols);
313 query.push_opt("start", self.start);
314 query.push_opt("end", self.end);
315 query.push_opt("limit", self.limit);
316 query.push_opt("feed", self.feed);
317 query.push_opt("sort", self.sort);
318 query.push_opt("asof", self.asof);
319 query.push_opt("currency", self.currency);
320 query.push_opt("page_token", self.page_token);
321 query.finish()
322 }
323}
324
325impl TradesSingleRequest {
326 pub(crate) fn validate(&self) -> Result<(), Error> {
327 validate_required_symbol(&self.symbol, "symbol")?;
328 validate_limit(self.limit, 1, 10_000)
329 }
330
331 pub(crate) fn into_query(self) -> Vec<(String, String)> {
332 let mut query = QueryWriter::default();
333 query.push_opt("start", self.start);
334 query.push_opt("end", self.end);
335 query.push_opt("limit", self.limit);
336 query.push_opt("feed", self.feed);
337 query.push_opt("sort", self.sort);
338 query.push_opt("asof", self.asof);
339 query.push_opt("currency", self.currency);
340 query.push_opt("page_token", self.page_token);
341 query.finish()
342 }
343}
344
345impl LatestBarsRequest {
346 pub(crate) fn validate(&self) -> Result<(), Error> {
347 validate_required_symbols(&self.symbols)
348 }
349
350 pub(crate) fn into_query(self) -> Vec<(String, String)> {
351 latest_batch_query(self.symbols, self.feed, self.currency)
352 }
353}
354
355impl LatestBarRequest {
356 pub(crate) fn validate(&self) -> Result<(), Error> {
357 validate_required_symbol(&self.symbol, "symbol")
358 }
359
360 pub(crate) fn into_query(self) -> Vec<(String, String)> {
361 latest_single_query(self.feed, self.currency)
362 }
363}
364
365impl LatestQuotesRequest {
366 pub(crate) fn validate(&self) -> Result<(), Error> {
367 validate_required_symbols(&self.symbols)
368 }
369
370 pub(crate) fn into_query(self) -> Vec<(String, String)> {
371 latest_batch_query(self.symbols, self.feed, self.currency)
372 }
373}
374
375impl LatestQuoteRequest {
376 pub(crate) fn validate(&self) -> Result<(), Error> {
377 validate_required_symbol(&self.symbol, "symbol")
378 }
379
380 pub(crate) fn into_query(self) -> Vec<(String, String)> {
381 latest_single_query(self.feed, self.currency)
382 }
383}
384
385impl LatestTradesRequest {
386 pub(crate) fn validate(&self) -> Result<(), Error> {
387 validate_required_symbols(&self.symbols)
388 }
389
390 pub(crate) fn into_query(self) -> Vec<(String, String)> {
391 latest_batch_query(self.symbols, self.feed, self.currency)
392 }
393}
394
395impl LatestTradeRequest {
396 pub(crate) fn validate(&self) -> Result<(), Error> {
397 validate_required_symbol(&self.symbol, "symbol")
398 }
399
400 pub(crate) fn into_query(self) -> Vec<(String, String)> {
401 latest_single_query(self.feed, self.currency)
402 }
403}
404
405impl SnapshotsRequest {
406 pub(crate) fn validate(&self) -> Result<(), Error> {
407 validate_required_symbols(&self.symbols)
408 }
409
410 pub(crate) fn into_query(self) -> Vec<(String, String)> {
411 latest_batch_query(self.symbols, self.feed, self.currency)
412 }
413}
414
415impl SnapshotRequest {
416 pub(crate) fn validate(&self) -> Result<(), Error> {
417 validate_required_symbol(&self.symbol, "symbol")
418 }
419
420 pub(crate) fn into_query(self) -> Vec<(String, String)> {
421 latest_single_query(self.feed, self.currency)
422 }
423}
424
425impl ConditionCodesRequest {
426 pub(crate) fn into_query(self) -> Vec<(String, String)> {
427 let mut query = QueryWriter::default();
428 query.push_opt("tape", Some(self.tape));
429 query.finish()
430 }
431}
432
433impl PaginatedRequest for BarsRequest {
434 fn with_page_token(&self, page_token: Option<String>) -> Self {
435 let mut next = self.clone();
436 next.page_token = page_token;
437 next
438 }
439}
440
441impl PaginatedRequest for BarsSingleRequest {
442 fn with_page_token(&self, page_token: Option<String>) -> Self {
443 let mut next = self.clone();
444 next.page_token = page_token;
445 next
446 }
447}
448
449impl PaginatedRequest for AuctionsRequest {
450 fn with_page_token(&self, page_token: Option<String>) -> Self {
451 let mut next = self.clone();
452 next.page_token = page_token;
453 next
454 }
455}
456
457impl PaginatedRequest for AuctionsSingleRequest {
458 fn with_page_token(&self, page_token: Option<String>) -> Self {
459 let mut next = self.clone();
460 next.page_token = page_token;
461 next
462 }
463}
464
465impl PaginatedRequest for QuotesRequest {
466 fn with_page_token(&self, page_token: Option<String>) -> Self {
467 let mut next = self.clone();
468 next.page_token = page_token;
469 next
470 }
471}
472
473impl PaginatedRequest for QuotesSingleRequest {
474 fn with_page_token(&self, page_token: Option<String>) -> Self {
475 let mut next = self.clone();
476 next.page_token = page_token;
477 next
478 }
479}
480
481impl PaginatedRequest for TradesRequest {
482 fn with_page_token(&self, page_token: Option<String>) -> Self {
483 let mut next = self.clone();
484 next.page_token = page_token;
485 next
486 }
487}
488
489impl PaginatedRequest for TradesSingleRequest {
490 fn with_page_token(&self, page_token: Option<String>) -> Self {
491 let mut next = self.clone();
492 next.page_token = page_token;
493 next
494 }
495}
496
497fn latest_batch_query(
498 symbols: Vec<String>,
499 feed: Option<DataFeed>,
500 currency: Option<Currency>,
501) -> Vec<(String, String)> {
502 let mut query = QueryWriter::default();
503 query.push_csv("symbols", symbols);
504 query.push_opt("feed", feed);
505 query.push_opt("currency", currency);
506 query.finish()
507}
508
509fn latest_single_query(
510 feed: Option<DataFeed>,
511 currency: Option<Currency>,
512) -> Vec<(String, String)> {
513 let mut query = QueryWriter::default();
514 query.push_opt("feed", feed);
515 query.push_opt("currency", currency);
516 query.finish()
517}
518
519fn validate_required_symbol(symbol: &str, field_name: &str) -> Result<(), Error> {
520 if symbol.trim().is_empty() {
521 return Err(Error::InvalidRequest(format!(
522 "{field_name} is invalid: must not be empty or whitespace-only"
523 )));
524 }
525
526 Ok(())
527}
528
529fn validate_required_symbols(symbols: &[String]) -> Result<(), Error> {
530 if symbols.is_empty() {
531 return Err(Error::InvalidRequest(
532 "symbols are invalid: must not be empty".to_owned(),
533 ));
534 }
535
536 if symbols.iter().any(|symbol| symbol.trim().is_empty()) {
537 return Err(Error::InvalidRequest(
538 "symbols are invalid: must not contain empty or whitespace-only entries".to_owned(),
539 ));
540 }
541
542 Ok(())
543}
544
545fn validate_limit(limit: Option<u32>, min: u32, max: u32) -> Result<(), Error> {
546 if let Some(limit) = limit
547 && !(min..=max).contains(&limit)
548 {
549 return Err(Error::InvalidRequest(format!(
550 "limit must be between {min} and {max}"
551 )));
552 }
553
554 Ok(())
555}