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, Default, Builder)]
53#[builder(default)]
54#[builder(setter(into, strip_option))]
55#[serde(rename_all = "camelCase")]
56pub struct GetPositionTiersParams {
57 pub inst_type: OKXInstrumentType,
59 pub td_mode: OKXTradeMode,
61 #[serde(skip_serializing_if = "Option::is_none")]
64 pub uly: Option<String>,
65 #[serde(skip_serializing_if = "Option::is_none")]
68 pub inst_family: Option<String>,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub inst_id: Option<String>,
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub ccy: Option<String>,
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub tier: Option<String>,
78}
79
80#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
82#[builder(default)]
83#[builder(setter(into, strip_option))]
84#[serde(rename_all = "camelCase")]
85pub struct GetInstrumentsParams {
86 pub inst_type: OKXInstrumentType,
88 #[serde(skip_serializing_if = "Option::is_none")]
91 pub uly: Option<String>,
92 #[serde(skip_serializing_if = "Option::is_none")]
95 pub inst_family: Option<String>,
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub inst_id: Option<String>,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub series_id: Option<String>,
102}
103
104#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
106#[builder(default)]
107#[builder(setter(into, strip_option))]
108#[serde(rename_all = "camelCase")]
109pub struct GetSpreadsParams {
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub base_ccy: Option<String>,
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub inst_id: Option<String>,
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub sprd_id: Option<String>,
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub state: Option<String>,
122}
123
124#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
126#[builder(default)]
127#[builder(setter(into, strip_option))]
128#[serde(rename_all = "camelCase")]
129pub struct GetSpreadOrderParams {
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub ord_id: Option<String>,
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub cl_ord_id: Option<String>,
136}
137
138#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
140#[builder(default)]
141#[builder(setter(into, strip_option))]
142#[serde(rename_all = "camelCase")]
143pub struct GetSpreadOrdersParams {
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub sprd_id: Option<String>,
147 #[serde(skip_serializing_if = "Option::is_none")]
149 pub ord_type: Option<OKXOrderType>,
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub state: Option<OKXOrderStatus>,
153 #[serde(skip_serializing_if = "Option::is_none")]
155 pub begin_id: Option<String>,
156 #[serde(skip_serializing_if = "Option::is_none")]
158 pub end_id: Option<String>,
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub begin: Option<String>,
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub end: Option<String>,
165 #[serde(skip_serializing_if = "Option::is_none")]
167 pub limit: Option<u32>,
168}
169
170#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
172#[builder(default)]
173#[builder(setter(into, strip_option))]
174#[serde(rename_all = "camelCase")]
175pub struct GetSpreadTradesParams {
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub sprd_id: Option<String>,
179 #[serde(skip_serializing_if = "Option::is_none")]
181 pub trade_id: Option<String>,
182 #[serde(skip_serializing_if = "Option::is_none")]
184 pub ord_id: Option<String>,
185 #[serde(skip_serializing_if = "Option::is_none")]
187 pub begin_id: Option<String>,
188 #[serde(skip_serializing_if = "Option::is_none")]
190 pub end_id: Option<String>,
191 #[serde(skip_serializing_if = "Option::is_none")]
193 pub begin: Option<String>,
194 #[serde(skip_serializing_if = "Option::is_none")]
196 pub end: Option<String>,
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub limit: Option<u32>,
200}
201
202#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
204#[builder(default)]
205#[builder(setter(into, strip_option))]
206#[serde(rename_all = "camelCase")]
207pub struct GetEventContractSeriesParams {
208 #[serde(skip_serializing_if = "Option::is_none")]
210 pub series_id: Option<String>,
211}
212
213#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
215#[builder(default)]
216#[builder(setter(into, strip_option))]
217#[serde(rename_all = "camelCase")]
218pub struct GetEventContractEventsParams {
219 pub series_id: String,
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub event_id: Option<String>,
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub state: Option<String>,
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub limit: Option<String>,
230 #[serde(skip_serializing_if = "Option::is_none")]
232 pub before: Option<String>,
233 #[serde(skip_serializing_if = "Option::is_none")]
235 pub after: Option<String>,
236}
237
238#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
240#[builder(default)]
241#[builder(setter(into, strip_option))]
242#[serde(rename_all = "camelCase")]
243pub struct GetEventContractMarketsParams {
244 pub series_id: String,
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub event_id: Option<String>,
249 #[serde(skip_serializing_if = "Option::is_none")]
251 pub inst_id: Option<String>,
252 #[serde(skip_serializing_if = "Option::is_none")]
254 pub state: Option<String>,
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub limit: Option<String>,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub before: Option<String>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 pub after: Option<String>,
264}
265
266#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
268#[builder(default)]
269#[builder(setter(into, strip_option))]
270#[serde(rename_all = "camelCase")]
271pub struct GetOptionSummaryParams {
272 pub inst_family: String,
274 #[serde(skip_serializing_if = "Option::is_none")]
276 pub exp_time: Option<String>,
277}
278
279#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
281#[builder(default)]
282#[builder(setter(into, strip_option))]
283#[serde(rename_all = "camelCase")]
284pub struct GetTradesParams {
285 pub inst_id: String,
287 #[serde(rename = "type")]
289 #[serde(skip_serializing_if = "Option::is_none")]
290 pub pagination_type: Option<u8>,
291 #[serde(skip_serializing_if = "Option::is_none")]
293 pub after: Option<String>,
294 #[serde(skip_serializing_if = "Option::is_none")]
296 pub before: Option<String>,
297 #[serde(skip_serializing_if = "Option::is_none")]
299 pub limit: Option<u32>,
300}
301
302#[derive(Clone, Debug, Deserialize, Serialize)]
304#[serde(rename_all = "camelCase")]
305pub struct GetCandlesticksParams {
306 pub inst_id: String,
308 pub bar: String,
310 #[serde(skip_serializing_if = "Option::is_none")]
312 #[serde(rename = "after")]
313 pub after_ms: Option<i64>,
314 #[serde(skip_serializing_if = "Option::is_none")]
316 #[serde(rename = "before")]
317 pub before_ms: Option<i64>,
318 #[serde(skip_serializing_if = "Option::is_none")]
320 pub limit: Option<u32>,
321}
322
323#[derive(Debug, Default)]
325pub struct GetCandlesticksParamsBuilder {
326 inst_id: Option<String>,
327 bar: Option<String>,
328 after_ms: Option<i64>,
329 before_ms: Option<i64>,
330 limit: Option<u32>,
331}
332
333impl GetCandlesticksParamsBuilder {
334 pub fn inst_id(&mut self, inst_id: impl Into<String>) -> &mut Self {
336 self.inst_id = Some(inst_id.into());
337 self
338 }
339
340 pub fn bar(&mut self, bar: impl Into<String>) -> &mut Self {
342 self.bar = Some(bar.into());
343 self
344 }
345
346 pub fn after_ms(&mut self, after_ms: i64) -> &mut Self {
348 self.after_ms = Some(after_ms);
349 self
350 }
351
352 pub fn before_ms(&mut self, before_ms: i64) -> &mut Self {
354 self.before_ms = Some(before_ms);
355 self
356 }
357
358 pub fn limit(&mut self, limit: u32) -> &mut Self {
360 self.limit = Some(limit);
361 self
362 }
363
364 pub fn build(&mut self) -> Result<GetCandlesticksParams, BuildError> {
370 let inst_id = self.inst_id.clone().ok_or(BuildError::MissingInstId)?;
372 let bar = self.bar.clone().ok_or(BuildError::MissingBar)?;
373 let after_ms = self.after_ms;
374 let before_ms = self.before_ms;
375 let limit = self.limit;
376
377 if let (Some(after), Some(before)) = (after_ms, before_ms)
391 && before >= after
392 {
393 return Err(BuildError::InvalidTimeRange {
394 after_ms: after,
395 before_ms: before,
396 });
397 }
398
399 if let Some(nanos) = after_ms
401 && nanos.abs() > 9_999_999_999_999
402 {
403 return Err(BuildError::CursorIsNanoseconds);
404 }
405
406 if let Some(nanos) = before_ms
407 && nanos.abs() > 9_999_999_999_999
408 {
409 return Err(BuildError::CursorIsNanoseconds);
410 }
411
412 if let Some(limit) = limit
416 && limit > 300
417 {
418 return Err(BuildError::LimitTooHigh);
419 }
420
421 Ok(GetCandlesticksParams {
422 inst_id,
423 bar,
424 after_ms,
425 before_ms,
426 limit,
427 })
428 }
429}
430
431#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
433#[builder(default)]
434#[builder(setter(into, strip_option))]
435#[serde(rename_all = "camelCase")]
436pub struct GetMarkPriceParams {
437 pub inst_type: OKXInstrumentType,
439 #[serde(skip_serializing_if = "Option::is_none")]
442 pub uly: Option<String>,
443 #[serde(skip_serializing_if = "Option::is_none")]
446 pub inst_family: Option<String>,
447 #[serde(skip_serializing_if = "Option::is_none")]
449 pub inst_id: Option<String>,
450}
451
452#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
454#[builder(default)]
455#[builder(setter(into, strip_option))]
456#[serde(rename_all = "camelCase")]
457pub struct GetPriceLimitParams {
458 pub inst_id: String,
460}
461
462#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
464#[builder(default)]
465#[builder(setter(into, strip_option))]
466#[serde(rename_all = "camelCase")]
467pub struct GetIndexTickerParams {
468 #[serde(skip_serializing_if = "Option::is_none")]
470 pub inst_id: Option<String>,
471 #[serde(skip_serializing_if = "Option::is_none")]
473 pub quote_ccy: Option<String>,
474}
475
476#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
478#[builder(default)]
479#[builder(setter(into, strip_option))]
480#[serde(rename_all = "camelCase")]
481pub struct GetOrderBookParams {
482 pub inst_id: String,
484 #[serde(skip_serializing_if = "Option::is_none")]
486 pub sz: Option<u32>,
487}
488
489#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
491#[builder(default)]
492#[builder(setter(into, strip_option))]
493#[serde(rename_all = "camelCase")]
494pub struct GetRpiOrderBookParams {
495 pub inst_id: String,
497 #[serde(skip_serializing_if = "Option::is_none")]
499 pub sz: Option<u32>,
500}
501
502#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
504#[builder(default)]
505#[builder(setter(into, strip_option))]
506#[serde(rename_all = "camelCase")]
507pub struct GetFundingRateHistoryParams {
508 pub inst_id: String,
510 #[serde(skip_serializing_if = "Option::is_none")]
512 pub before: Option<String>,
513 #[serde(skip_serializing_if = "Option::is_none")]
515 pub after: Option<String>,
516 #[serde(skip_serializing_if = "Option::is_none")]
518 pub limit: Option<u32>,
519}
520
521#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
523#[builder(default)]
524#[builder(setter(into, strip_option))]
525#[serde(rename_all = "camelCase")]
526pub struct GetOrderHistoryParams {
527 pub inst_type: OKXInstrumentType,
529 #[serde(skip_serializing_if = "Option::is_none")]
531 pub uly: Option<String>,
532 #[serde(skip_serializing_if = "Option::is_none")]
534 pub inst_family: Option<String>,
535 #[serde(skip_serializing_if = "Option::is_none")]
537 pub inst_id: Option<String>,
538 #[serde(skip_serializing_if = "Option::is_none")]
540 pub ord_type: Option<OKXOrderType>,
541 #[serde(skip_serializing_if = "Option::is_none")]
543 pub state: Option<String>,
544 #[serde(skip_serializing_if = "Option::is_none")]
546 pub after: Option<String>,
547 #[serde(skip_serializing_if = "Option::is_none")]
549 pub before: Option<String>,
550 #[serde(skip_serializing_if = "Option::is_none")]
552 pub limit: Option<u32>,
553}
554
555#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
557#[builder(default)]
558#[builder(setter(into, strip_option))]
559#[serde(rename_all = "camelCase")]
560pub struct GetOrderListParams {
561 #[serde(skip_serializing_if = "Option::is_none")]
563 pub inst_type: Option<OKXInstrumentType>,
564 #[serde(skip_serializing_if = "Option::is_none")]
566 pub inst_id: Option<String>,
567 #[serde(skip_serializing_if = "Option::is_none")]
569 pub inst_family: Option<String>,
570 #[serde(skip_serializing_if = "Option::is_none")]
572 pub state: Option<OKXOrderStatus>,
573 #[serde(skip_serializing_if = "Option::is_none")]
575 pub after: Option<String>,
576 #[serde(skip_serializing_if = "Option::is_none")]
578 pub before: Option<String>,
579 #[serde(skip_serializing_if = "Option::is_none")]
581 pub limit: Option<u32>,
582}
583
584#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
586#[builder(default)]
587#[builder(setter(into, strip_option))]
588#[serde(rename_all = "camelCase")]
589pub struct GetAlgoOrderParams {
590 #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
592 pub algo_id: Option<String>,
593 #[serde(rename = "algoClOrdId", skip_serializing_if = "Option::is_none")]
595 pub algo_cl_ord_id: Option<String>,
596}
597
598#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
600#[builder(default)]
601#[builder(setter(into, strip_option))]
602#[serde(rename_all = "camelCase")]
603pub struct GetAlgoOrdersParams {
604 #[serde(rename = "algoId", skip_serializing_if = "Option::is_none")]
606 pub algo_id: Option<String>,
607 #[serde(rename = "algoClOrdId", skip_serializing_if = "Option::is_none")]
609 pub algo_cl_ord_id: Option<String>,
610 pub inst_type: OKXInstrumentType,
612 #[serde(rename = "instId", skip_serializing_if = "Option::is_none")]
614 pub inst_id: Option<String>,
615 #[serde(rename = "ordType", skip_serializing_if = "Option::is_none")]
617 pub ord_type: Option<OKXAlgoOrderType>,
618 #[serde(skip_serializing_if = "Option::is_none")]
620 pub state: Option<OKXAlgoOrderStatus>,
621 #[serde(skip_serializing_if = "Option::is_none")]
623 pub after: Option<String>,
624 #[serde(skip_serializing_if = "Option::is_none")]
626 pub before: Option<String>,
627 #[serde(skip_serializing_if = "Option::is_none")]
629 pub limit: Option<u32>,
630}
631
632#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
634#[builder(default)]
635#[builder(setter(into, strip_option))]
636#[serde(rename_all = "camelCase")]
637pub struct GetTransactionDetailsParams {
638 #[serde(skip_serializing_if = "Option::is_none")]
640 pub inst_type: Option<OKXInstrumentType>,
641 #[serde(skip_serializing_if = "Option::is_none")]
643 pub inst_id: Option<String>,
644 #[serde(skip_serializing_if = "Option::is_none")]
646 pub ord_id: Option<String>,
647 #[serde(skip_serializing_if = "Option::is_none")]
649 pub after: Option<String>,
650 #[serde(skip_serializing_if = "Option::is_none")]
652 pub before: Option<String>,
653 #[serde(skip_serializing_if = "Option::is_none")]
655 pub begin: Option<String>,
656 #[serde(skip_serializing_if = "Option::is_none")]
658 pub end: Option<String>,
659 #[serde(skip_serializing_if = "Option::is_none")]
661 pub limit: Option<u32>,
662}
663
664#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
666#[builder(default)]
667#[builder(setter(into, strip_option))]
668#[serde(rename_all = "camelCase")]
669pub struct GetPositionsParams {
670 #[serde(skip_serializing_if = "Option::is_none")]
672 pub inst_type: Option<OKXInstrumentType>,
673 #[serde(skip_serializing_if = "Option::is_none")]
675 pub inst_id: Option<String>,
676 #[serde(skip_serializing_if = "Option::is_none")]
678 pub pos_id: Option<String>,
679}
680
681#[derive(Clone, Debug, Deserialize, Serialize, Default, Builder)]
683#[builder(default)]
684#[builder(setter(into, strip_option))]
685#[serde(rename_all = "camelCase")]
686pub struct GetPositionsHistoryParams {
687 pub inst_type: OKXInstrumentType,
689 #[serde(skip_serializing_if = "Option::is_none")]
691 pub inst_id: Option<String>,
692 #[serde(skip_serializing_if = "Option::is_none")]
694 pub pos_id: Option<String>,
695 #[serde(skip_serializing_if = "Option::is_none")]
697 pub after: Option<String>,
698 #[serde(skip_serializing_if = "Option::is_none")]
700 pub before: Option<String>,
701 #[serde(skip_serializing_if = "Option::is_none")]
703 pub limit: Option<u32>,
704}
705
706#[derive(Clone, Debug, Default, Deserialize, Serialize, Builder)]
708#[builder(default)]
709#[builder(setter(into, strip_option))]
710#[serde(rename_all = "camelCase")]
711pub struct GetOrderParams {
712 #[serde(skip_serializing)]
714 pub inst_type: OKXInstrumentType,
715 pub inst_id: String,
717 #[serde(skip_serializing_if = "Option::is_none")]
719 pub ord_id: Option<String>,
720 #[serde(skip_serializing_if = "Option::is_none")]
722 pub cl_ord_id: Option<String>,
723 #[serde(skip_serializing)]
725 pub pos_side: Option<OKXPositionSide>,
726}
727
728#[derive(Clone, Debug, Deserialize, Serialize, Builder)]
730#[builder(setter(into, strip_option))]
731#[serde(rename_all = "camelCase")]
732pub struct GetTradeFeeParams {
733 pub inst_type: OKXInstrumentType,
735 #[serde(skip_serializing_if = "Option::is_none")]
737 pub uly: Option<String>,
738 #[serde(skip_serializing_if = "Option::is_none")]
740 pub inst_family: Option<String>,
741}
742
743#[cfg(test)]
744mod tests {
745 use rstest::rstest;
746
747 use super::*;
748
749 #[rstest]
750 fn test_optional_parameters_are_omitted_when_none() {
751 let mut builder = GetCandlesticksParamsBuilder::default();
752 builder.inst_id("BTC-USDT-SWAP");
753 builder.bar("1m");
754
755 let params = builder.build().unwrap();
756 let qs = serde_urlencoded::to_string(¶ms).unwrap();
757 assert_eq!(
758 qs, "instId=BTC-USDT-SWAP&bar=1m",
759 "unexpected optional parameters were serialized: {qs}",
760 );
761 }
762
763 #[rstest]
764 fn test_no_literal_none_strings_leak_into_query_string() {
765 let mut builder = GetCandlesticksParamsBuilder::default();
766 builder.inst_id("BTC-USDT-SWAP");
767 builder.bar("1m");
768
769 let params = builder.build().unwrap();
770 let qs = serde_urlencoded::to_string(¶ms).unwrap();
771 assert!(
772 !qs.contains("None"),
773 "found literal \"None\" in query string: {qs}",
774 );
775 assert!(
776 !qs.contains("after=") && !qs.contains("before=") && !qs.contains("limit="),
777 "empty optional parameters must be omitted entirely: {qs}",
778 );
779 }
780
781 #[rstest]
782 fn test_cursor_nanoseconds_rejected() {
783 let after_nanos = 1_725_307_200_000_000_000i64;
785
786 let mut builder = GetCandlesticksParamsBuilder::default();
787 builder.inst_id("BTC-USDT-SWAP");
788 builder.bar("1m");
789 builder.after_ms(after_nanos);
790
791 let result = builder.build();
793 assert!(result.is_err());
794 assert!(result.unwrap_err().to_string().contains("nanoseconds"));
795 }
796
797 #[rstest]
798 fn test_both_cursors_rejected() {
799 let mut builder = GetCandlesticksParamsBuilder::default();
800 builder.inst_id("BTC-USDT-SWAP");
801 builder.bar("1m");
802 builder.after_ms(1725307200000);
805 builder.before_ms(1725393600000);
806
807 let result = builder.build();
808 assert!(result.is_err());
809 assert!(result.unwrap_err().to_string().contains("time range"));
810 }
811
812 #[rstest]
813 fn test_limit_exceeds_maximum_rejected() {
814 let mut builder = GetCandlesticksParamsBuilder::default();
815 builder.inst_id("BTC-USDT-SWAP");
816 builder.bar("1m");
817 builder.limit(301u32); let result = builder.build();
821 assert!(result.is_err());
822 assert!(result.unwrap_err().to_string().contains("300"));
823 }
824
825 #[rstest]
826 #[case(1725307200000, "after=1725307200000")] #[case(1725307200, "after=1725307200")] #[case(1725307, "after=1725307")] fn test_valid_millisecond_cursor_passes(#[case] timestamp: i64, #[case] expected: &str) {
830 let mut builder = GetCandlesticksParamsBuilder::default();
831 builder.inst_id("BTC-USDT-SWAP");
832 builder.bar("1m");
833 builder.after_ms(timestamp);
834
835 let params = builder.build().unwrap();
836 let qs = serde_urlencoded::to_string(¶ms).unwrap();
837 assert!(qs.contains(expected));
838 }
839
840 #[rstest]
841 #[case(1, "limit=1")]
842 #[case(50, "limit=50")]
843 #[case(100, "limit=100")]
844 #[case(300, "limit=300")] fn test_valid_limit_passes(#[case] limit: u32, #[case] expected: &str) {
846 let mut builder = GetCandlesticksParamsBuilder::default();
847 builder.inst_id("BTC-USDT-SWAP");
848 builder.bar("1m");
849 builder.limit(limit);
850
851 let params = builder.build().unwrap();
852 let qs = serde_urlencoded::to_string(¶ms).unwrap();
853 assert!(qs.contains(expected));
854 }
855}