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