1use crate::common::query::QueryWriter;
2use crate::transport::pagination::PaginatedRequest;
3
4use super::{ContractType, OptionsFeed, Sort, TickType, TimeFrame};
5
6#[derive(Clone, Debug, Default)]
7pub struct BarsRequest {
8 pub symbols: Vec<String>,
9 pub timeframe: TimeFrame,
10 pub start: Option<String>,
11 pub end: Option<String>,
12 pub limit: Option<u32>,
13 pub sort: Option<Sort>,
14 pub page_token: Option<String>,
15}
16
17#[derive(Clone, Debug, Default)]
18pub struct TradesRequest {
19 pub symbols: Vec<String>,
20 pub start: Option<String>,
21 pub end: Option<String>,
22 pub limit: Option<u32>,
23 pub sort: Option<Sort>,
24 pub page_token: Option<String>,
25}
26
27#[derive(Clone, Debug, Default)]
28pub struct LatestQuotesRequest {
29 pub symbols: Vec<String>,
30 pub feed: Option<OptionsFeed>,
31}
32
33#[derive(Clone, Debug, Default)]
34pub struct LatestTradesRequest {
35 pub symbols: Vec<String>,
36 pub feed: Option<OptionsFeed>,
37}
38
39#[derive(Clone, Debug, Default)]
40pub struct SnapshotsRequest {
41 pub symbols: Vec<String>,
42 pub feed: Option<OptionsFeed>,
43 pub limit: Option<u32>,
44 pub page_token: Option<String>,
45}
46
47#[derive(Clone, Debug, Default)]
48pub struct ChainRequest {
49 pub underlying_symbol: String,
50 pub feed: Option<OptionsFeed>,
51 pub r#type: Option<ContractType>,
52 pub strike_price_gte: Option<f64>,
53 pub strike_price_lte: Option<f64>,
54 pub expiration_date: Option<String>,
55 pub expiration_date_gte: Option<String>,
56 pub expiration_date_lte: Option<String>,
57 pub root_symbol: Option<String>,
58 pub updated_since: Option<String>,
59 pub limit: Option<u32>,
60 pub page_token: Option<String>,
61}
62
63#[derive(Clone, Debug, Default)]
64pub struct ConditionCodesRequest {
65 pub ticktype: TickType,
66}
67
68impl BarsRequest {
69 pub(crate) fn to_query(self) -> Vec<(String, String)> {
70 let mut query = QueryWriter::default();
71 query.push_csv("symbols", self.symbols);
72 query.push_opt("timeframe", Some(self.timeframe));
73 query.push_opt("start", self.start);
74 query.push_opt("end", self.end);
75 query.push_opt("limit", self.limit);
76 query.push_opt("page_token", self.page_token);
77 query.push_opt("sort", self.sort);
78 query.finish()
79 }
80}
81
82impl TradesRequest {
83 pub(crate) fn to_query(self) -> Vec<(String, String)> {
84 let mut query = QueryWriter::default();
85 query.push_csv("symbols", self.symbols);
86 query.push_opt("start", self.start);
87 query.push_opt("end", self.end);
88 query.push_opt("limit", self.limit);
89 query.push_opt("page_token", self.page_token);
90 query.push_opt("sort", self.sort);
91 query.finish()
92 }
93}
94
95impl LatestQuotesRequest {
96 #[allow(dead_code)]
97 pub(crate) fn to_query(self) -> Vec<(String, String)> {
98 latest_query(self.symbols, self.feed)
99 }
100}
101
102impl LatestTradesRequest {
103 #[allow(dead_code)]
104 pub(crate) fn to_query(self) -> Vec<(String, String)> {
105 latest_query(self.symbols, self.feed)
106 }
107}
108
109impl SnapshotsRequest {
110 #[allow(dead_code)]
111 pub(crate) fn to_query(self) -> Vec<(String, String)> {
112 let mut query = QueryWriter::default();
113 query.push_csv("symbols", self.symbols);
114 query.push_opt("feed", self.feed);
115 query.push_opt("limit", self.limit);
116 query.push_opt("page_token", self.page_token);
117 query.finish()
118 }
119}
120
121impl ChainRequest {
122 #[allow(dead_code)]
123 pub(crate) fn to_query(self) -> Vec<(String, String)> {
124 let mut query = QueryWriter::default();
125 query.push_opt("feed", self.feed);
126 query.push_opt("type", self.r#type);
127 query.push_opt("strike_price_gte", self.strike_price_gte);
128 query.push_opt("strike_price_lte", self.strike_price_lte);
129 query.push_opt("expiration_date", self.expiration_date);
130 query.push_opt("expiration_date_gte", self.expiration_date_gte);
131 query.push_opt("expiration_date_lte", self.expiration_date_lte);
132 query.push_opt("root_symbol", self.root_symbol);
133 query.push_opt("updated_since", self.updated_since);
134 query.push_opt("limit", self.limit);
135 query.push_opt("page_token", self.page_token);
136 query.finish()
137 }
138}
139
140impl ConditionCodesRequest {
141 pub(crate) fn ticktype(&self) -> &'static str {
142 self.ticktype.as_str()
143 }
144}
145
146impl PaginatedRequest for BarsRequest {
147 fn with_page_token(&self, page_token: Option<String>) -> Self {
148 let mut next = self.clone();
149 next.page_token = page_token;
150 next
151 }
152}
153
154impl PaginatedRequest for TradesRequest {
155 fn with_page_token(&self, page_token: Option<String>) -> Self {
156 let mut next = self.clone();
157 next.page_token = page_token;
158 next
159 }
160}
161
162impl PaginatedRequest for SnapshotsRequest {
163 fn with_page_token(&self, page_token: Option<String>) -> Self {
164 let mut next = self.clone();
165 next.page_token = page_token;
166 next
167 }
168}
169
170impl PaginatedRequest for ChainRequest {
171 fn with_page_token(&self, page_token: Option<String>) -> Self {
172 let mut next = self.clone();
173 next.page_token = page_token;
174 next
175 }
176}
177
178#[allow(dead_code)]
179fn latest_query(symbols: Vec<String>, feed: Option<OptionsFeed>) -> Vec<(String, String)> {
180 let mut query = QueryWriter::default();
181 query.push_csv("symbols", symbols);
182 query.push_opt("feed", feed);
183 query.finish()
184}
185
186#[cfg(test)]
187mod tests {
188 use super::{
189 BarsRequest, ChainRequest, ConditionCodesRequest, ContractType, LatestQuotesRequest,
190 LatestTradesRequest, OptionsFeed, SnapshotsRequest, Sort, TickType, TimeFrame,
191 TradesRequest,
192 };
193
194 #[test]
195 fn bars_request_serializes_official_query_words() {
196 let query = BarsRequest {
197 symbols: vec!["AAPL260406C00180000".into(), "AAPL260406C00185000".into()],
198 timeframe: TimeFrame::from("1Day"),
199 start: Some("2026-04-01T00:00:00Z".into()),
200 end: Some("2026-04-03T00:00:00Z".into()),
201 limit: Some(2),
202 sort: Some(Sort::Asc),
203 page_token: Some("page-2".into()),
204 }
205 .to_query();
206
207 assert_eq!(
208 query,
209 vec![
210 (
211 "symbols".to_string(),
212 "AAPL260406C00180000,AAPL260406C00185000".to_string(),
213 ),
214 ("timeframe".to_string(), "1Day".to_string()),
215 ("start".to_string(), "2026-04-01T00:00:00Z".to_string()),
216 ("end".to_string(), "2026-04-03T00:00:00Z".to_string()),
217 ("limit".to_string(), "2".to_string()),
218 ("page_token".to_string(), "page-2".to_string()),
219 ("sort".to_string(), "asc".to_string()),
220 ]
221 );
222 }
223
224 #[test]
225 fn trades_request_serializes_official_query_words() {
226 let query = TradesRequest {
227 symbols: vec!["AAPL260406C00180000".into()],
228 start: Some("2026-04-02T13:39:00Z".into()),
229 end: Some("2026-04-02T13:40:00Z".into()),
230 limit: Some(1),
231 sort: Some(Sort::Desc),
232 page_token: Some("page-3".into()),
233 }
234 .to_query();
235
236 assert_eq!(
237 query,
238 vec![
239 ("symbols".to_string(), "AAPL260406C00180000".to_string()),
240 ("start".to_string(), "2026-04-02T13:39:00Z".to_string()),
241 ("end".to_string(), "2026-04-02T13:40:00Z".to_string()),
242 ("limit".to_string(), "1".to_string()),
243 ("page_token".to_string(), "page-3".to_string()),
244 ("sort".to_string(), "desc".to_string()),
245 ]
246 );
247 }
248
249 #[test]
250 fn latest_requests_serialize_official_query_words() {
251 let quotes_query = LatestQuotesRequest {
252 symbols: vec!["AAPL260406C00180000".into()],
253 feed: Some(OptionsFeed::Indicative),
254 }
255 .to_query();
256 assert_eq!(
257 quotes_query,
258 vec![
259 ("symbols".to_string(), "AAPL260406C00180000".to_string()),
260 ("feed".to_string(), "indicative".to_string()),
261 ]
262 );
263
264 let trades_query = LatestTradesRequest {
265 symbols: vec!["AAPL260406C00180000".into(), "AAPL260406C00185000".into()],
266 feed: Some(OptionsFeed::Opra),
267 }
268 .to_query();
269 assert_eq!(
270 trades_query,
271 vec![
272 (
273 "symbols".to_string(),
274 "AAPL260406C00180000,AAPL260406C00185000".to_string(),
275 ),
276 ("feed".to_string(), "opra".to_string()),
277 ]
278 );
279 }
280
281 #[test]
282 fn snapshot_requests_serialize_official_query_words() {
283 let query = SnapshotsRequest {
284 symbols: vec!["AAPL260406C00180000".into(), "AAPL260406C00185000".into()],
285 feed: Some(OptionsFeed::Indicative),
286 limit: Some(2),
287 page_token: Some("page-2".into()),
288 }
289 .to_query();
290
291 assert_eq!(
292 query,
293 vec![
294 (
295 "symbols".to_string(),
296 "AAPL260406C00180000,AAPL260406C00185000".to_string(),
297 ),
298 ("feed".to_string(), "indicative".to_string()),
299 ("limit".to_string(), "2".to_string()),
300 ("page_token".to_string(), "page-2".to_string()),
301 ]
302 );
303 }
304
305 #[test]
306 fn chain_request_serializes_official_query_words() {
307 let query = ChainRequest {
308 underlying_symbol: "AAPL".into(),
309 feed: Some(OptionsFeed::Indicative),
310 r#type: Some(ContractType::Call),
311 strike_price_gte: Some(180.0),
312 strike_price_lte: Some(200.0),
313 expiration_date: Some("2026-04-06".into()),
314 expiration_date_gte: Some("2026-04-06".into()),
315 expiration_date_lte: Some("2026-04-13".into()),
316 root_symbol: Some("AAPL".into()),
317 updated_since: Some("2026-04-02T19:30:00Z".into()),
318 limit: Some(3),
319 page_token: Some("page-3".into()),
320 }
321 .to_query();
322
323 assert_eq!(
324 query,
325 vec![
326 ("feed".to_string(), "indicative".to_string()),
327 ("type".to_string(), "call".to_string()),
328 ("strike_price_gte".to_string(), "180".to_string()),
329 ("strike_price_lte".to_string(), "200".to_string()),
330 ("expiration_date".to_string(), "2026-04-06".to_string()),
331 ("expiration_date_gte".to_string(), "2026-04-06".to_string()),
332 ("expiration_date_lte".to_string(), "2026-04-13".to_string()),
333 ("root_symbol".to_string(), "AAPL".to_string()),
334 (
335 "updated_since".to_string(),
336 "2026-04-02T19:30:00Z".to_string()
337 ),
338 ("limit".to_string(), "3".to_string()),
339 ("page_token".to_string(), "page-3".to_string()),
340 ]
341 );
342 }
343
344 #[test]
345 fn condition_codes_request_uses_official_ticktype_word() {
346 let trade = ConditionCodesRequest {
347 ticktype: TickType::Trade,
348 };
349 assert_eq!(trade.ticktype(), "trade");
350
351 let quote = ConditionCodesRequest {
352 ticktype: TickType::Quote,
353 };
354 assert_eq!(quote.ticktype(), "quote");
355 }
356}