1use derive_builder::Builder;
31use serde::{self, Deserialize, Serialize};
32
33use crate::{
34 common::enums::{
35 OKXAlgoOrderStatus, OKXAlgoOrderType, OKXInstrumentType, OKXOrderStatus, OKXOrderType,
36 OKXPositionMode, OKXPositionSide, OKXTradeMode,
37 },
38 http::error::BuildError,
39};
40
41#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
43#[builder(setter(into, strip_option))]
44#[serde(rename_all = "camelCase")]
45pub struct SetPositionModeParams {
46 #[serde(rename = "posMode")]
48 pub pos_mode: OKXPositionMode,
49}
50
51#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
53#[builder(setter(into, strip_option))]
54#[serde(rename_all = "camelCase")]
55pub struct ActivateFeatureParams {
56 pub feature: String,
58}
59
60#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
62#[builder(default)]
63#[builder(setter(into, strip_option))]
64#[serde(rename_all = "camelCase")]
65pub struct GetPositionTiersParams {
66 pub inst_type: OKXInstrumentType,
68 pub td_mode: OKXTradeMode,
70 #[serde(skip_serializing_if = "Option::is_none")]
73 pub uly: Option<String>,
74 #[serde(skip_serializing_if = "Option::is_none")]
77 pub inst_family: Option<String>,
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub inst_id: Option<String>,
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub ccy: Option<String>,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub tier: Option<String>,
87}
88
89#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
91#[builder(default)]
92#[builder(setter(into, strip_option))]
93#[serde(rename_all = "camelCase")]
94pub struct GetInstrumentsParams {
95 pub inst_type: OKXInstrumentType,
97 #[serde(skip_serializing_if = "Option::is_none")]
100 pub uly: Option<String>,
101 #[serde(skip_serializing_if = "Option::is_none")]
104 pub inst_family: Option<String>,
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub inst_id: Option<String>,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub series_id: Option<String>,
111}
112
113#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
115#[builder(default)]
116#[builder(setter(into, strip_option))]
117#[serde(rename_all = "camelCase")]
118pub struct GetSpreadsParams {
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub base_ccy: Option<String>,
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub inst_id: Option<String>,
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub sprd_id: Option<String>,
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub state: Option<String>,
131}
132
133#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
135#[builder(default)]
136#[builder(setter(into, strip_option))]
137#[serde(rename_all = "camelCase")]
138pub struct GetSpreadOrderParams {
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub ord_id: Option<String>,
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub cl_ord_id: Option<String>,
145}
146
147#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
149#[builder(default)]
150#[builder(setter(into, strip_option))]
151#[serde(rename_all = "camelCase")]
152pub struct GetSpreadOrdersParams {
153 #[serde(skip_serializing_if = "Option::is_none")]
155 pub sprd_id: Option<String>,
156 #[serde(skip_serializing_if = "Option::is_none")]
158 pub ord_type: Option<OKXOrderType>,
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub state: Option<OKXOrderStatus>,
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub begin_id: Option<String>,
165 #[serde(skip_serializing_if = "Option::is_none")]
167 pub end_id: Option<String>,
168 #[serde(skip_serializing_if = "Option::is_none")]
170 pub begin: Option<String>,
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub end: Option<String>,
174 #[serde(skip_serializing_if = "Option::is_none")]
176 pub limit: Option<u32>,
177}
178
179#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
181#[builder(default)]
182#[builder(setter(into, strip_option))]
183#[serde(rename_all = "camelCase")]
184pub struct GetSpreadTradesParams {
185 #[serde(skip_serializing_if = "Option::is_none")]
187 pub sprd_id: Option<String>,
188 #[serde(skip_serializing_if = "Option::is_none")]
190 pub trade_id: Option<String>,
191 #[serde(skip_serializing_if = "Option::is_none")]
193 pub ord_id: Option<String>,
194 #[serde(skip_serializing_if = "Option::is_none")]
196 pub begin_id: Option<String>,
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub end_id: Option<String>,
200 #[serde(skip_serializing_if = "Option::is_none")]
202 pub begin: Option<String>,
203 #[serde(skip_serializing_if = "Option::is_none")]
205 pub end: Option<String>,
206 #[serde(skip_serializing_if = "Option::is_none")]
208 pub limit: Option<u32>,
209}
210
211#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
213#[builder(default)]
214#[builder(setter(into, strip_option))]
215#[serde(rename_all = "camelCase")]
216pub struct GetEventContractSeriesParams {
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub series_id: Option<String>,
220}
221
222#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
224#[builder(default)]
225#[builder(setter(into, strip_option))]
226#[serde(rename_all = "camelCase")]
227pub struct GetEventContractEventsParams {
228 pub series_id: String,
230 #[serde(skip_serializing_if = "Option::is_none")]
232 pub event_id: Option<String>,
233 #[serde(skip_serializing_if = "Option::is_none")]
235 pub state: Option<String>,
236 #[serde(skip_serializing_if = "Option::is_none")]
238 pub limit: Option<String>,
239 #[serde(skip_serializing_if = "Option::is_none")]
241 pub before: Option<String>,
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub after: Option<String>,
245}
246
247#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
249#[builder(default)]
250#[builder(setter(into, strip_option))]
251#[serde(rename_all = "camelCase")]
252pub struct GetEventContractMarketsParams {
253 pub series_id: String,
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub event_id: Option<String>,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub inst_id: Option<String>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 pub state: Option<String>,
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub limit: Option<String>,
267 #[serde(skip_serializing_if = "Option::is_none")]
269 pub before: Option<String>,
270 #[serde(skip_serializing_if = "Option::is_none")]
272 pub after: Option<String>,
273}
274
275#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
277#[builder(default)]
278#[builder(setter(into, strip_option))]
279#[serde(rename_all = "camelCase")]
280pub struct GetOptionSummaryParams {
281 pub inst_family: String,
283 #[serde(skip_serializing_if = "Option::is_none")]
285 pub exp_time: Option<String>,
286}
287
288#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
290#[builder(default)]
291#[builder(setter(into, strip_option))]
292#[serde(rename_all = "camelCase")]
293pub struct GetTradesParams {
294 pub inst_id: String,
296 #[serde(rename = "type")]
298 #[serde(skip_serializing_if = "Option::is_none")]
299 pub pagination_type: Option<u8>,
300 #[serde(skip_serializing_if = "Option::is_none")]
302 pub after: Option<String>,
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub before: Option<String>,
306 #[serde(skip_serializing_if = "Option::is_none")]
308 pub limit: Option<u32>,
309}
310
311#[derive(Clone, Debug, Deserialize, Serialize)]
313#[serde(rename_all = "camelCase")]
314pub struct GetCandlesticksParams {
315 pub inst_id: String,
317 pub bar: String,
319 #[serde(skip_serializing_if = "Option::is_none")]
321 #[serde(rename = "after")]
322 pub after_ms: Option<i64>,
323 #[serde(skip_serializing_if = "Option::is_none")]
325 #[serde(rename = "before")]
326 pub before_ms: Option<i64>,
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub limit: Option<u32>,
330}
331
332#[derive(Debug, Default)]
334pub struct GetCandlesticksParamsBuilder {
335 inst_id: Option<String>,
336 bar: Option<String>,
337 after_ms: Option<i64>,
338 before_ms: Option<i64>,
339 limit: Option<u32>,
340}
341
342impl GetCandlesticksParamsBuilder {
343 pub fn inst_id(&mut self, inst_id: impl Into<String>) -> &mut Self {
345 self.inst_id = Some(inst_id.into());
346 self
347 }
348
349 pub fn bar(&mut self, bar: impl Into<String>) -> &mut Self {
351 self.bar = Some(bar.into());
352 self
353 }
354
355 pub fn after_ms(&mut self, after_ms: i64) -> &mut Self {
357 self.after_ms = Some(after_ms);
358 self
359 }
360
361 pub fn before_ms(&mut self, before_ms: i64) -> &mut Self {
363 self.before_ms = Some(before_ms);
364 self
365 }
366
367 pub fn limit(&mut self, limit: u32) -> &mut Self {
369 self.limit = Some(limit);
370 self
371 }
372
373 pub fn build(&mut self) -> Result<GetCandlesticksParams, BuildError> {
379 let inst_id = self.inst_id.clone().ok_or(BuildError::MissingInstId)?;
381 let bar = self.bar.clone().ok_or(BuildError::MissingBar)?;
382 let after_ms = self.after_ms;
383 let before_ms = self.before_ms;
384 let limit = self.limit;
385
386 if let (Some(after), Some(before)) = (after_ms, before_ms)
400 && before >= after
401 {
402 return Err(BuildError::InvalidTimeRange {
403 after_ms: after,
404 before_ms: before,
405 });
406 }
407
408 if let Some(nanos) = after_ms
410 && nanos.abs() > 9_999_999_999_999
411 {
412 return Err(BuildError::CursorIsNanoseconds);
413 }
414
415 if let Some(nanos) = before_ms
416 && nanos.abs() > 9_999_999_999_999
417 {
418 return Err(BuildError::CursorIsNanoseconds);
419 }
420
421 if let Some(limit) = limit
425 && limit > 300
426 {
427 return Err(BuildError::LimitTooHigh);
428 }
429
430 Ok(GetCandlesticksParams {
431 inst_id,
432 bar,
433 after_ms,
434 before_ms,
435 limit,
436 })
437 }
438}
439
440#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
442#[builder(default)]
443#[builder(setter(into, strip_option))]
444#[serde(rename_all = "camelCase")]
445pub struct GetMarkPriceParams {
446 pub inst_type: OKXInstrumentType,
448 #[serde(skip_serializing_if = "Option::is_none")]
451 pub uly: Option<String>,
452 #[serde(skip_serializing_if = "Option::is_none")]
455 pub inst_family: Option<String>,
456 #[serde(skip_serializing_if = "Option::is_none")]
458 pub inst_id: Option<String>,
459}
460
461#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
463#[builder(default)]
464#[builder(setter(into, strip_option))]
465#[serde(rename_all = "camelCase")]
466pub struct GetPriceLimitParams {
467 pub inst_id: String,
469}
470
471#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
473#[builder(default)]
474#[builder(setter(into, strip_option))]
475#[serde(rename_all = "camelCase")]
476pub struct GetIndexTickerParams {
477 #[serde(skip_serializing_if = "Option::is_none")]
479 pub inst_id: Option<String>,
480 #[serde(skip_serializing_if = "Option::is_none")]
482 pub quote_ccy: Option<String>,
483}
484
485#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
487#[builder(default)]
488#[builder(setter(into, strip_option))]
489#[serde(rename_all = "camelCase")]
490pub struct GetOrderBookParams {
491 pub inst_id: String,
493 #[serde(skip_serializing_if = "Option::is_none")]
495 pub sz: Option<u32>,
496}
497
498#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
500#[builder(default)]
501#[builder(setter(into, strip_option))]
502#[serde(rename_all = "camelCase")]
503pub struct GetRpiOrderBookParams {
504 pub inst_id: String,
506 #[serde(skip_serializing_if = "Option::is_none")]
508 pub sz: Option<u32>,
509}
510
511#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
513#[builder(default)]
514#[builder(setter(into, strip_option))]
515#[serde(rename_all = "camelCase")]
516pub struct GetFundingRateHistoryParams {
517 pub inst_id: String,
519 #[serde(skip_serializing_if = "Option::is_none")]
521 pub before: Option<String>,
522 #[serde(skip_serializing_if = "Option::is_none")]
524 pub after: Option<String>,
525 #[serde(skip_serializing_if = "Option::is_none")]
527 pub limit: Option<u32>,
528}
529
530#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
532#[builder(default)]
533#[builder(setter(into, strip_option))]
534#[serde(rename_all = "camelCase")]
535pub struct GetOrderHistoryParams {
536 pub inst_type: OKXInstrumentType,
538 #[serde(skip_serializing_if = "Option::is_none")]
540 pub uly: Option<String>,
541 #[serde(skip_serializing_if = "Option::is_none")]
543 pub inst_family: Option<String>,
544 #[serde(skip_serializing_if = "Option::is_none")]
546 pub inst_id: Option<String>,
547 #[serde(skip_serializing_if = "Option::is_none")]
549 pub ord_type: Option<OKXOrderType>,
550 #[serde(skip_serializing_if = "Option::is_none")]
552 pub state: Option<String>,
553 #[serde(skip_serializing_if = "Option::is_none")]
555 pub after: Option<String>,
556 #[serde(skip_serializing_if = "Option::is_none")]
558 pub before: Option<String>,
559 #[serde(skip_serializing_if = "Option::is_none")]
561 pub limit: Option<u32>,
562}
563
564#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
566#[builder(default)]
567#[builder(setter(into, strip_option))]
568#[serde(rename_all = "camelCase")]
569pub struct GetOrderListParams {
570 #[serde(skip_serializing_if = "Option::is_none")]
572 pub inst_type: Option<OKXInstrumentType>,
573 #[serde(skip_serializing_if = "Option::is_none")]
575 pub inst_id: Option<String>,
576 #[serde(skip_serializing_if = "Option::is_none")]
578 pub inst_family: Option<String>,
579 #[serde(skip_serializing_if = "Option::is_none")]
581 pub state: Option<OKXOrderStatus>,
582 #[serde(skip_serializing_if = "Option::is_none")]
584 pub after: Option<String>,
585 #[serde(skip_serializing_if = "Option::is_none")]
587 pub before: Option<String>,
588 #[serde(skip_serializing_if = "Option::is_none")]
590 pub limit: Option<u32>,
591}
592
593#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
595#[builder(default)]
596#[builder(setter(into, strip_option))]
597#[serde(rename_all = "camelCase")]
598pub struct GetAlgoOrderParams {
599 #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
601 pub algo_id: Option<String>,
602 #[serde(rename = "algoClOrdId", skip_serializing_if = "Option::is_none")]
604 pub algo_cl_ord_id: Option<String>,
605}
606
607#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
609#[builder(default)]
610#[builder(setter(into, strip_option))]
611#[serde(rename_all = "camelCase")]
612pub struct GetAlgoOrdersParams {
613 #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
615 pub algo_id: Option<String>,
616 #[serde(rename = "algoClOrdId", skip_serializing_if = "Option::is_none")]
618 pub algo_cl_ord_id: Option<String>,
619 pub inst_type: OKXInstrumentType,
621 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
623 pub inst_id: Option<String>,
624 #[serde(rename = "ordType", skip_serializing_if = "Option::is_none")]
626 pub ord_type: Option<OKXAlgoOrderType>,
627 #[serde(skip_serializing_if = "Option::is_none")]
629 pub state: Option<OKXAlgoOrderStatus>,
630 #[serde(skip_serializing_if = "Option::is_none")]
632 pub after: Option<String>,
633 #[serde(skip_serializing_if = "Option::is_none")]
635 pub before: Option<String>,
636 #[serde(skip_serializing_if = "Option::is_none")]
638 pub limit: Option<u32>,
639}
640
641#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
643#[builder(default)]
644#[builder(setter(into, strip_option))]
645#[serde(rename_all = "camelCase")]
646pub struct GetTransactionDetailsParams {
647 #[serde(skip_serializing_if = "Option::is_none")]
649 pub inst_type: Option<OKXInstrumentType>,
650 #[serde(skip_serializing_if = "Option::is_none")]
652 pub inst_id: Option<String>,
653 #[serde(skip_serializing_if = "Option::is_none")]
655 pub ord_id: Option<String>,
656 #[serde(skip_serializing_if = "Option::is_none")]
658 pub after: Option<String>,
659 #[serde(skip_serializing_if = "Option::is_none")]
661 pub before: Option<String>,
662 #[serde(skip_serializing_if = "Option::is_none")]
664 pub begin: Option<String>,
665 #[serde(skip_serializing_if = "Option::is_none")]
667 pub end: Option<String>,
668 #[serde(skip_serializing_if = "Option::is_none")]
670 pub limit: Option<u32>,
671}
672
673#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
675#[builder(default)]
676#[builder(setter(into, strip_option))]
677#[serde(rename_all = "camelCase")]
678pub struct GetPositionsParams {
679 #[serde(skip_serializing_if = "Option::is_none")]
681 pub inst_type: Option<OKXInstrumentType>,
682 #[serde(skip_serializing_if = "Option::is_none")]
684 pub inst_id: Option<String>,
685 #[serde(skip_serializing_if = "Option::is_none")]
687 pub pos_id: Option<String>,
688}
689
690#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
692#[builder(default)]
693#[builder(setter(into, strip_option))]
694#[serde(rename_all = "camelCase")]
695pub struct GetPositionsHistoryParams {
696 pub inst_type: OKXInstrumentType,
698 #[serde(skip_serializing_if = "Option::is_none")]
700 pub inst_id: Option<String>,
701 #[serde(skip_serializing_if = "Option::is_none")]
703 pub pos_id: Option<String>,
704 #[serde(skip_serializing_if = "Option::is_none")]
706 pub after: Option<String>,
707 #[serde(skip_serializing_if = "Option::is_none")]
709 pub before: Option<String>,
710 #[serde(skip_serializing_if = "Option::is_none")]
712 pub limit: Option<u32>,
713}
714
715#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
717#[builder(default)]
718#[builder(setter(into, strip_option))]
719#[serde(rename_all = "camelCase")]
720pub struct GetOrderParams {
721 #[serde(skip_serializing)]
723 pub inst_type: OKXInstrumentType,
724 pub inst_id: String,
726 #[serde(skip_serializing_if = "Option::is_none")]
728 pub ord_id: Option<String>,
729 #[serde(skip_serializing_if = "Option::is_none")]
731 pub cl_ord_id: Option<String>,
732 #[serde(skip_serializing)]
734 pub pos_side: Option<OKXPositionSide>,
735}
736
737#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
739#[builder(setter(into, strip_option))]
740#[serde(rename_all = "camelCase")]
741pub struct GetTradeFeeParams {
742 pub inst_type: OKXInstrumentType,
744 #[serde(skip_serializing_if = "Option::is_none")]
746 pub uly: Option<String>,
747 #[serde(skip_serializing_if = "Option::is_none")]
749 pub inst_family: Option<String>,
750}
751
752#[cfg(test)]
753mod tests {
754 use rstest::rstest;
755
756 use super::*;
757
758 #[rstest]
759 fn test_optional_parameters_are_omitted_when_none() {
760 let mut builder = GetCandlesticksParamsBuilder::default();
761 builder.inst_id("BTC-USDT-SWAP");
762 builder.bar("1m");
763
764 let params = builder.build().unwrap();
765 let qs = serde_urlencoded::to_string(¶ms).unwrap();
766 assert_eq!(
767 qs, "instId=BTC-USDT-SWAP&bar=1m",
768 "unexpected optional parameters were serialized: {qs}",
769 );
770 }
771
772 #[rstest]
773 fn test_no_literal_none_strings_leak_into_query_string() {
774 let mut builder = GetCandlesticksParamsBuilder::default();
775 builder.inst_id("BTC-USDT-SWAP");
776 builder.bar("1m");
777
778 let params = builder.build().unwrap();
779 let qs = serde_urlencoded::to_string(¶ms).unwrap();
780 assert!(
781 !qs.contains("None"),
782 "found literal \"None\" in query string: {qs}",
783 );
784 assert!(
785 !qs.contains("after=") && !qs.contains("before=") && !qs.contains("limit="),
786 "empty optional parameters must be omitted entirely: {qs}",
787 );
788 }
789
790 #[rstest]
791 fn test_cursor_nanoseconds_rejected() {
792 let after_nanos = 1_725_307_200_000_000_000i64;
794
795 let mut builder = GetCandlesticksParamsBuilder::default();
796 builder.inst_id("BTC-USDT-SWAP");
797 builder.bar("1m");
798 builder.after_ms(after_nanos);
799
800 let result = builder.build();
802 assert!(result.is_err());
803 assert!(result.unwrap_err().to_string().contains("nanoseconds"));
804 }
805
806 #[rstest]
807 fn test_both_cursors_rejected() {
808 let mut builder = GetCandlesticksParamsBuilder::default();
809 builder.inst_id("BTC-USDT-SWAP");
810 builder.bar("1m");
811 builder.after_ms(1_725_307_200_000);
814 builder.before_ms(1_725_393_600_000);
815
816 let result = builder.build();
817 assert!(result.is_err());
818 assert!(result.unwrap_err().to_string().contains("time range"));
819 }
820
821 #[rstest]
822 fn test_limit_exceeds_maximum_rejected() {
823 let mut builder = GetCandlesticksParamsBuilder::default();
824 builder.inst_id("BTC-USDT-SWAP");
825 builder.bar("1m");
826 builder.limit(301u32); let result = builder.build();
830 assert!(result.is_err());
831 assert!(result.unwrap_err().to_string().contains("300"));
832 }
833
834 #[rstest]
835 #[case(1_725_307_200_000, "after=1725307200000")] #[case(1_725_307_200, "after=1725307200")] #[case(1_725_307, "after=1725307")] fn test_valid_millisecond_cursor_passes(#[case] timestamp: i64, #[case] expected: &str) {
839 let mut builder = GetCandlesticksParamsBuilder::default();
840 builder.inst_id("BTC-USDT-SWAP");
841 builder.bar("1m");
842 builder.after_ms(timestamp);
843
844 let params = builder.build().unwrap();
845 let qs = serde_urlencoded::to_string(¶ms).unwrap();
846 assert!(qs.contains(expected));
847 }
848
849 #[rstest]
850 #[case(1, "limit=1")]
851 #[case(50, "limit=50")]
852 #[case(100, "limit=100")]
853 #[case(300, "limit=300")] fn test_valid_limit_passes(#[case] limit: u32, #[case] expected: &str) {
855 let mut builder = GetCandlesticksParamsBuilder::default();
856 builder.inst_id("BTC-USDT-SWAP");
857 builder.bar("1m");
858 builder.limit(limit);
859
860 let params = builder.build().unwrap();
861 let qs = serde_urlencoded::to_string(¶ms).unwrap();
862 assert!(qs.contains(expected));
863 }
864}