1use ahash::AHashMap;
19use jiff::{Timestamp, civil::Date};
20use rust_decimal::Decimal;
21use serde::{Deserialize, Serialize};
22use strum::{AsRefStr, Display};
23use ustr::Ustr;
24
25use crate::common::{
26 enums::{
27 AxCandleWidth, AxCategory, AxFundingSlotStatus, AxFundingVariant, AxInstrumentState,
28 AxOrderSide, AxOrderStatus, AxTimeInForce,
29 },
30 parse::{
31 deserialize_decimal_or_zero, deserialize_optional_decimal,
32 deserialize_optional_decimal_from_str, serialize_decimal_as_str,
33 serialize_optional_decimal_as_str,
34 },
35};
36
37fn default_instrument_state() -> AxInstrumentState {
39 AxInstrumentState::Open
40}
41
42#[derive(Clone, Debug, Serialize, Deserialize)]
49#[serde(rename_all = "snake_case")]
50pub struct AxWhoAmIAccount {
51 pub id: String,
53 pub name: String,
55 pub is_close_only: bool,
57 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
59 pub maker_fee: Option<Decimal>,
60 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
62 pub taker_fee: Option<Decimal>,
63 pub can_list: bool,
65 pub can_read: bool,
67 pub can_set_limits: bool,
69 pub can_reduce_or_close: bool,
71 pub can_trade: bool,
73}
74
75#[derive(Clone, Debug, Serialize, Deserialize)]
80#[serde(rename_all = "snake_case")]
81pub struct AxWhoAmI {
82 pub id: String,
84 pub username: String,
86 pub created_at: Timestamp,
88 pub require_2fa: bool,
90 pub is_onboarded: bool,
92 pub is_frozen: bool,
94 pub is_admin: bool,
96 pub accounts: Vec<AxWhoAmIAccount>,
98 #[serde(default)]
100 pub pseudonym: Option<String>,
101 #[serde(default)]
103 pub fiat_deposit_code: Option<String>,
104}
105
106#[derive(Clone, Debug, Serialize, Deserialize)]
111#[serde(rename_all = "snake_case")]
112pub struct AxInstrument {
113 pub symbol: Ustr,
115 #[serde(default)]
117 pub product: Option<Ustr>,
118 #[serde(default = "default_instrument_state")]
120 pub state: AxInstrumentState,
121 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
123 pub multiplier: Decimal,
124 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
126 pub minimum_order_size: Decimal,
127 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
129 pub tick_size: Decimal,
130 pub quote_currency: Ustr,
132 pub funding_settlement_currency: Ustr,
134 pub category: AxCategory,
136 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
138 pub maintenance_margin_pct: Decimal,
139 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
141 pub initial_margin_pct: Decimal,
142 #[serde(default)]
144 pub contract_mark_price: Option<String>,
145 #[serde(default)]
147 pub contract_size: Option<String>,
148 #[serde(default)]
150 pub description: Option<String>,
151 #[serde(default)]
153 pub expiration: Option<Timestamp>,
154 #[serde(default)]
156 pub funding_calendar_schedule: Option<String>,
157 #[serde(default)]
159 pub funding_frequency: Option<String>,
160 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
162 pub funding_rate_cap_lower_pct: Option<Decimal>,
163 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
165 pub funding_rate_cap_upper_pct: Option<Decimal>,
166 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
168 pub price_band_lower_deviation_pct: Option<Decimal>,
169 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
171 pub price_band_upper_deviation_pct: Option<Decimal>,
172 #[serde(default)]
174 pub price_bands: Option<String>,
175 #[serde(default)]
177 pub price_quotation: Option<String>,
178 #[serde(default)]
180 pub underlying_benchmark_price: Option<String>,
181}
182
183#[derive(Clone, Debug, Serialize, Deserialize)]
188#[serde(rename_all = "snake_case")]
189pub struct AxInstrumentsResponse {
190 pub instruments: Vec<AxInstrument>,
192}
193
194#[derive(Clone, Debug, Serialize, Deserialize)]
199#[serde(rename_all = "snake_case")]
200pub struct AxBalance {
201 pub symbol: Ustr,
203 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
205 pub amount: Decimal,
206}
207
208#[derive(Clone, Debug, Serialize, Deserialize)]
213#[serde(rename_all = "snake_case")]
214pub struct AxBalancesResponse {
215 pub balances: Vec<AxBalance>,
217}
218
219#[derive(Clone, Debug, Serialize, Deserialize)]
224#[serde(rename_all = "snake_case")]
225pub struct AxPosition {
226 pub account_id: Ustr,
228 pub symbol: Ustr,
230 pub signed_quantity: i64,
232 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
234 pub signed_notional: Decimal,
235 pub timestamp: Timestamp,
237 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
239 pub realized_pnl: Decimal,
240}
241
242#[derive(Clone, Debug, Serialize, Deserialize)]
247#[serde(rename_all = "snake_case")]
248pub struct AxPositionsResponse {
249 pub positions: Vec<AxPosition>,
251}
252
253#[derive(Clone, Debug, Serialize, Deserialize)]
258#[serde(rename_all = "snake_case")]
259pub struct AxTicker {
260 #[serde(rename = "s")]
262 pub symbol: Ustr,
263 #[serde(
265 default,
266 rename = "bp",
267 deserialize_with = "deserialize_optional_decimal"
268 )]
269 pub bid: Option<Decimal>,
270 #[serde(
272 default,
273 rename = "ap",
274 deserialize_with = "deserialize_optional_decimal"
275 )]
276 pub ask: Option<Decimal>,
277 #[serde(
279 default,
280 rename = "p",
281 deserialize_with = "deserialize_optional_decimal"
282 )]
283 pub last: Option<Decimal>,
284 #[serde(
286 default,
287 rename = "m",
288 deserialize_with = "deserialize_optional_decimal"
289 )]
290 pub mark: Option<Decimal>,
291 #[serde(
293 default,
294 rename = "v",
295 deserialize_with = "deserialize_optional_decimal"
296 )]
297 pub volume_24h: Option<Decimal>,
298 #[serde(
300 default,
301 rename = "h",
302 deserialize_with = "deserialize_optional_decimal"
303 )]
304 pub high_24h: Option<Decimal>,
305 #[serde(
307 default,
308 rename = "l",
309 deserialize_with = "deserialize_optional_decimal"
310 )]
311 pub low_24h: Option<Decimal>,
312 #[serde(default)]
314 pub ts: Option<i64>,
315 #[serde(default)]
317 pub tn: Option<i64>,
318 #[serde(default, rename = "q")]
320 pub last_quantity: Option<u64>,
321 #[serde(default, rename = "oi")]
323 pub open_interest: Option<i64>,
324 #[serde(default, rename = "i")]
326 pub instrument_state: Option<AxInstrumentState>,
327 #[serde(
329 default,
330 rename = "pl",
331 deserialize_with = "deserialize_optional_decimal"
332 )]
333 pub price_band_lower: Option<Decimal>,
334 #[serde(
336 default,
337 rename = "pu",
338 deserialize_with = "deserialize_optional_decimal"
339 )]
340 pub price_band_upper: Option<Decimal>,
341 #[serde(
343 default,
344 rename = "lsp",
345 deserialize_with = "deserialize_optional_decimal"
346 )]
347 pub last_settlement_price: Option<Decimal>,
348 #[serde(default, rename = "lst")]
350 pub last_settlement_time: Option<i64>,
351}
352
353#[derive(Clone, Debug, Serialize, Deserialize)]
358#[serde(rename_all = "snake_case")]
359pub struct AxTickersResponse {
360 pub tickers: Vec<AxTicker>,
362 pub total_count: i64,
364 pub limit: i32,
366 pub offset: i32,
368}
369
370#[derive(Clone, Debug, Serialize, Deserialize)]
375#[serde(rename_all = "snake_case")]
376pub struct AxTickerResponse {
377 pub ticker: AxTicker,
379}
380
381#[derive(Clone, Debug, Serialize, Deserialize)]
386#[serde(rename_all = "snake_case")]
387pub struct AxAuthenticateResponse {
388 pub token: String,
390}
391
392#[derive(Clone, Debug, Serialize, Deserialize)]
397pub struct AxPlaceOrderResponse {
398 pub oid: String,
400}
401
402#[derive(Clone, Debug, Serialize, Deserialize)]
407pub struct AxCancelOrderResponse {
408 pub cxl_rx: bool,
410}
411
412#[derive(Clone, Debug, Serialize, Deserialize)]
417pub struct AxRestTrade {
418 pub ts: i64,
420 pub tn: i64,
422 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
424 pub p: Decimal,
425 pub q: i64,
427 pub s: Ustr,
429 pub d: AxOrderSide,
431}
432
433#[derive(Clone, Debug, Serialize, Deserialize)]
438pub struct AxTradesResponse {
439 pub trades: Vec<AxRestTrade>,
441}
442
443#[derive(Clone, Debug, Serialize, Deserialize)]
448pub struct AxBookLevel {
449 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
451 pub p: Decimal,
452 pub q: i64,
454 #[serde(default)]
456 pub o: Option<Vec<i64>>,
457}
458
459#[derive(Clone, Debug, Serialize, Deserialize)]
464pub struct AxBook {
465 pub ts: i64,
467 pub tn: i64,
469 pub s: Ustr,
471 pub b: Vec<AxBookLevel>,
473 pub a: Vec<AxBookLevel>,
475}
476
477#[derive(Clone, Debug, Serialize, Deserialize)]
482pub struct AxBookResponse {
483 pub book: AxBook,
485}
486
487#[derive(Clone, Debug, Serialize, Deserialize)]
492pub struct AxOrderStatusDetail {
493 pub symbol: Ustr,
495 pub order_id: String,
497 pub state: AxOrderStatus,
499 #[serde(default)]
501 pub clord_id: Option<u64>,
502 #[serde(default)]
504 pub filled_quantity: Option<i64>,
505 #[serde(default)]
507 pub remaining_quantity: Option<i64>,
508 #[serde(default)]
510 pub reject_reason: Option<AxOrderRejectReason>,
511 #[serde(default)]
513 pub reject_message: Option<String>,
514}
515
516#[derive(Clone, Debug, Serialize, Deserialize)]
521pub struct AxOrderStatusQueryResponse {
522 pub status: AxOrderStatusDetail,
524}
525
526#[derive(Clone, Copy, Debug, Display, Eq, PartialEq, Hash, AsRefStr, Serialize, Deserialize)]
531#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
532#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
533pub enum AxOrderRejectReason {
534 CloseOnly,
535 InsufficientMargin,
536 MaxOpenOrdersExceeded,
537 UnknownSymbol,
538 ExchangeClosed,
539 IncorrectQuantity,
540 InvalidPriceIncrement,
541 IncorrectOrderType,
542 PriceOutOfBounds,
543 NoLiquidity,
544 InsufficientCreditLimit,
545 #[serde(other)]
546 Unknown,
547}
548
549#[derive(Clone, Debug, Serialize, Deserialize)]
554pub struct AxOrderDetail {
555 pub ts: i64,
557 #[serde(default)]
559 pub tn: i64,
560 pub oid: String,
562 #[serde(default)]
564 pub aid: Option<String>,
565 pub u: String,
567 pub s: Ustr,
569 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
571 pub p: Decimal,
572 pub q: u64,
574 pub xq: u64,
576 pub rq: u64,
578 pub o: AxOrderStatus,
580 pub d: AxOrderSide,
582 pub tif: AxTimeInForce,
584 #[serde(default)]
586 pub cid: Option<u64>,
587 #[serde(default)]
589 pub r: Option<AxOrderRejectReason>,
590 #[serde(default)]
592 pub tag: Option<String>,
593 #[serde(default)]
595 pub txt: Option<String>,
596 #[serde(default)]
598 pub po: bool,
599}
600
601#[derive(Clone, Debug, Serialize, Deserialize)]
606pub struct AxOrdersResponse {
607 pub orders: Vec<AxOrderDetail>,
609 #[serde(default)]
611 pub total_count: Option<i64>,
612 #[serde(default)]
614 pub limit: Option<i32>,
615 #[serde(default)]
617 pub offset: Option<i32>,
618 #[serde(default)]
620 pub next_cursor: Option<String>,
621}
622
623#[derive(Clone, Debug, Serialize, Deserialize)]
628pub struct AxInitialMarginRequirementResponse {
629 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
631 pub im: Decimal,
632}
633
634#[derive(Clone, Debug, Serialize, Deserialize)]
639pub struct AxOpenOrder {
640 pub tn: i64,
642 pub ts: i64,
644 pub d: AxOrderSide,
646 pub o: AxOrderStatus,
648 pub oid: String,
650 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
652 pub p: Decimal,
653 pub q: u64,
655 pub rq: u64,
657 pub s: Ustr,
659 pub tif: AxTimeInForce,
661 pub u: String,
663 pub xq: u64,
665 #[serde(default)]
667 pub cid: Option<u64>,
668 #[serde(default)]
670 pub tag: Option<String>,
671 #[serde(default)]
673 pub po: bool,
674}
675
676#[derive(Clone, Debug, Serialize, Deserialize)]
681pub struct AxOpenOrdersResponse {
682 pub orders: Vec<AxOpenOrder>,
684 pub total_count: i64,
686 pub limit: i32,
688 pub offset: i32,
690}
691
692#[derive(Clone, Debug, Serialize, Deserialize)]
697#[serde(rename_all = "snake_case")]
698pub struct AxFill {
699 pub trade_id: String,
701 pub order_id: Option<String>,
703 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
705 pub fee: Decimal,
706 pub is_taker: bool,
708 pub is_block_trade: Option<bool>,
710 pub is_final_settlement: Option<bool>,
712 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
714 pub price: Decimal,
715 pub quantity: u64,
717 pub side: AxOrderSide,
719 pub symbol: Ustr,
721 pub timestamp: Timestamp,
723 pub account_id: Ustr,
725 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
727 pub realized_pnl: Option<Decimal>,
728}
729
730#[derive(Clone, Debug, Serialize, Deserialize)]
735#[serde(rename_all = "snake_case")]
736pub struct AxFillsResponse {
737 pub fills: Vec<AxFill>,
739 #[serde(default)]
741 pub total_count: Option<i64>,
742 #[serde(default)]
744 pub limit: Option<i32>,
745 #[serde(default)]
747 pub next_cursor: Option<String>,
748}
749
750#[derive(Clone, Debug, Serialize, Deserialize)]
755#[serde(rename_all = "snake_case")]
756pub struct AxCandle {
757 pub symbol: Ustr,
759 pub ts: i64,
761 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
763 pub open: Decimal,
764 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
766 pub high: Decimal,
767 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
769 pub low: Decimal,
770 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
772 pub close: Decimal,
773 pub buy_volume: u64,
775 pub sell_volume: u64,
777 pub volume: u64,
779 pub width: AxCandleWidth,
781}
782
783#[derive(Clone, Debug, Serialize, Deserialize)]
788#[serde(rename_all = "snake_case")]
789pub struct AxCandlesResponse {
790 pub candles: Vec<AxCandle>,
792}
793
794#[derive(Clone, Debug, Serialize, Deserialize)]
800#[serde(rename_all = "snake_case")]
801pub struct AxCandleResponse {
802 pub candle: AxCandle,
804}
805
806#[derive(Clone, Debug, Serialize, Deserialize)]
811#[serde(rename_all = "snake_case")]
812pub struct AxFundingRate {
813 pub symbol: Ustr,
815 pub timestamp_ns: i64,
817 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
819 pub funding_rate: Decimal,
820 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
822 pub funding_amount: Decimal,
823 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
825 pub benchmark_price: Decimal,
826 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
828 pub settlement_price: Decimal,
829}
830
831#[derive(Clone, Debug, Serialize, Deserialize)]
836#[serde(rename_all = "snake_case")]
837pub struct AxFundingRatesResponse {
838 pub funding_rates: Vec<AxFundingRate>,
840 #[serde(default)]
842 pub total_count: Option<i64>,
843 #[serde(default)]
845 pub limit: Option<i32>,
846 #[serde(default)]
848 pub next_cursor: Option<String>,
849}
850
851#[derive(Clone, Debug, Serialize, Deserialize)]
856#[serde(rename_all = "snake_case")]
857pub struct AxFundingSlot {
858 pub index: i32,
860 pub funding_time: Timestamp,
862 pub status: AxFundingSlotStatus,
864 pub capped: bool,
866 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
868 pub mark_twap: Option<Decimal>,
869 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
871 pub underlying_twap: Option<Decimal>,
872 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
874 pub premium_bps: Option<Decimal>,
875 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
877 pub funding_rate_bps: Option<Decimal>,
878 #[serde(default)]
880 pub reason: Option<String>,
881}
882
883#[derive(Clone, Debug, Serialize, Deserialize)]
891#[serde(rename_all = "snake_case")]
892pub struct AxFundingSlotsResponse {
893 pub symbol: Ustr,
895 pub date: Date,
897 pub timezone: String,
899 pub variant: AxFundingVariant,
901 pub interval_count: i32,
903 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
905 pub cap_bps: Option<Decimal>,
906 pub slots: Vec<AxFundingSlot>,
908 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
910 pub realized_sum_bps: Decimal,
911 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
913 pub projected_eod_bps: Decimal,
914}
915
916#[derive(Clone, Debug, Serialize, Deserialize)]
921#[serde(rename_all = "snake_case")]
922pub struct AxPerSymbolRisk {
923 pub signed_quantity: i64,
925 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
927 pub signed_notional: Decimal,
928 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
930 pub average_price: Option<Decimal>,
931 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
933 pub liquidation_price: Option<Decimal>,
934 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
936 pub initial_margin_required_position: Decimal,
937 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
939 pub initial_margin_required_open_orders: Decimal,
940 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
942 pub initial_margin_required_total: Decimal,
943 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
945 pub maintenance_margin_required: Decimal,
946 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
948 pub unrealized_pnl: Decimal,
949}
950
951#[derive(Clone, Debug, Serialize, Deserialize)]
956#[serde(rename_all = "snake_case")]
957pub struct AxRiskSnapshot {
958 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
960 pub balance_usd: Decimal,
961 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
963 pub equity: Decimal,
964 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
966 pub initial_margin_available: Decimal,
967 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
969 pub initial_margin_required_for_open_orders: Decimal,
970 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
972 pub initial_margin_required_for_positions: Decimal,
973 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
975 pub initial_margin_required_total: Decimal,
976 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
978 pub maintenance_margin_available: Decimal,
979 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
981 pub maintenance_margin_required: Decimal,
982 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
984 pub unrealized_pnl: Decimal,
985 pub timestamp_ns: Timestamp,
987 pub account_id: Ustr,
989 #[serde(default)]
991 pub per_symbol: AHashMap<String, AxPerSymbolRisk>,
992}
993
994#[derive(Clone, Debug, Serialize, Deserialize)]
999#[serde(rename_all = "snake_case")]
1000pub struct AxRiskSnapshotResponse {
1001 pub risk_snapshot: AxRiskSnapshot,
1003}
1004
1005#[derive(Clone, Debug, Serialize, Deserialize)]
1010#[serde(rename_all = "snake_case")]
1011pub struct AxTransaction {
1012 pub account_id: Ustr,
1014 #[serde(deserialize_with = "deserialize_decimal_or_zero")]
1016 pub amount: Decimal,
1017 pub event_id: String,
1019 pub symbol: Ustr,
1021 pub timestamp: Timestamp,
1023 pub transaction_type: Ustr,
1025 #[serde(default)]
1027 pub initiated_by_user_id: Option<String>,
1028 #[serde(default)]
1030 pub reference_id: Option<String>,
1031}
1032
1033#[derive(Clone, Debug, Serialize, Deserialize)]
1038#[serde(rename_all = "snake_case")]
1039pub struct AxTransactionsResponse {
1040 pub transactions: Vec<AxTransaction>,
1042 #[serde(default)]
1044 pub total_count: Option<i64>,
1045 #[serde(default)]
1047 pub limit: Option<i32>,
1048 #[serde(default)]
1050 pub next_cursor: Option<String>,
1051}
1052
1053#[derive(Clone, Debug, Serialize, Deserialize)]
1058#[serde(rename_all = "snake_case")]
1059pub struct AuthenticateApiKeyRequest {
1060 pub api_key: String,
1062 pub api_secret: String,
1064 pub expiration_seconds: i32,
1066}
1067
1068impl AuthenticateApiKeyRequest {
1069 #[must_use]
1071 pub fn new(
1072 api_key: impl Into<String>,
1073 api_secret: impl Into<String>,
1074 expiration_seconds: i32,
1075 ) -> Self {
1076 Self {
1077 api_key: api_key.into(),
1078 api_secret: api_secret.into(),
1079 expiration_seconds,
1080 }
1081 }
1082}
1083
1084#[derive(Clone, Debug, Serialize, Deserialize)]
1089pub struct PlaceOrderRequest {
1090 pub d: AxOrderSide,
1092 #[serde(serialize_with = "serialize_decimal_as_str")]
1094 pub p: Decimal,
1095 pub po: bool,
1097 pub q: u64,
1099 pub s: Ustr,
1101 pub tif: AxTimeInForce,
1103 #[serde(skip_serializing_if = "Option::is_none")]
1105 pub tag: Option<String>,
1106}
1107
1108impl PlaceOrderRequest {
1109 #[must_use]
1111 pub fn new(
1112 side: AxOrderSide,
1113 price: Decimal,
1114 quantity: u64,
1115 symbol: Ustr,
1116 time_in_force: AxTimeInForce,
1117 post_only: bool,
1118 ) -> Self {
1119 Self {
1120 d: side,
1121 p: price,
1122 po: post_only,
1123 q: quantity,
1124 s: symbol,
1125 tif: time_in_force,
1126 tag: None,
1127 }
1128 }
1129
1130 #[must_use]
1132 pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
1133 self.tag = Some(tag.into());
1134 self
1135 }
1136}
1137
1138#[derive(Clone, Debug, Serialize, Deserialize)]
1143pub struct PreviewAggressiveLimitOrderRequest {
1144 pub symbol: Ustr,
1146 pub quantity: u64,
1148 pub side: AxOrderSide,
1150}
1151
1152impl PreviewAggressiveLimitOrderRequest {
1153 #[must_use]
1155 pub fn new(symbol: Ustr, quantity: u64, side: AxOrderSide) -> Self {
1156 Self {
1157 symbol,
1158 quantity,
1159 side,
1160 }
1161 }
1162}
1163
1164#[derive(Clone, Debug, Serialize, Deserialize)]
1169pub struct AxPreviewAggressiveLimitOrderResponse {
1170 pub filled_quantity: u64,
1172 pub remaining_quantity: u64,
1174 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
1176 pub limit_price: Option<Decimal>,
1177 #[serde(default, deserialize_with = "deserialize_optional_decimal_from_str")]
1179 pub vwap: Option<Decimal>,
1180}
1181
1182#[derive(Clone, Debug, Serialize, Deserialize)]
1187pub struct CancelOrderRequest {
1188 pub oid: String,
1190}
1191
1192impl CancelOrderRequest {
1193 #[must_use]
1195 pub fn new(order_id: impl Into<String>) -> Self {
1196 Self {
1197 oid: order_id.into(),
1198 }
1199 }
1200}
1201
1202#[derive(Clone, Debug, Serialize, Deserialize)]
1210pub struct ReplaceOrderRequest {
1211 pub oid: String,
1213 #[serde(
1215 skip_serializing_if = "Option::is_none",
1216 serialize_with = "serialize_optional_decimal_as_str"
1217 )]
1218 pub p: Option<Decimal>,
1219 #[serde(skip_serializing_if = "Option::is_none")]
1221 pub q: Option<u64>,
1222 #[serde(skip_serializing_if = "Option::is_none")]
1224 pub po: Option<bool>,
1225 #[serde(skip_serializing_if = "Option::is_none")]
1227 pub tif: Option<AxTimeInForce>,
1228}
1229
1230impl ReplaceOrderRequest {
1231 #[must_use]
1235 pub fn new(order_id: impl Into<String>) -> Self {
1236 Self {
1237 oid: order_id.into(),
1238 p: None,
1239 q: None,
1240 po: None,
1241 tif: None,
1242 }
1243 }
1244
1245 #[must_use]
1247 pub fn with_price(mut self, price: Decimal) -> Self {
1248 self.p = Some(price);
1249 self
1250 }
1251
1252 #[must_use]
1254 pub fn with_quantity(mut self, quantity: u64) -> Self {
1255 self.q = Some(quantity);
1256 self
1257 }
1258}
1259
1260#[derive(Clone, Debug, Serialize, Deserialize)]
1265pub struct AxReplaceOrderResponse {
1266 pub oid: String,
1268}
1269
1270#[derive(Clone, Debug, Default, Serialize, Deserialize)]
1275pub struct CancelAllOrdersRequest {
1276 #[serde(skip_serializing_if = "Option::is_none")]
1278 pub account_id: Option<Ustr>,
1279 #[serde(skip_serializing_if = "Option::is_none")]
1281 pub symbol: Option<Ustr>,
1282}
1283
1284impl CancelAllOrdersRequest {
1285 #[must_use]
1287 pub fn new() -> Self {
1288 Self::default()
1289 }
1290
1291 #[must_use]
1293 pub fn with_account_id(mut self, account_id: Ustr) -> Self {
1294 self.account_id = Some(account_id);
1295 self
1296 }
1297
1298 #[must_use]
1300 pub fn with_symbol(mut self, symbol: Ustr) -> Self {
1301 self.symbol = Some(symbol);
1302 self
1303 }
1304}
1305
1306#[derive(Clone, Debug, Serialize, Deserialize)]
1311pub struct AxCancelAllOrdersResponse {}
1312
1313#[cfg(test)]
1314mod tests {
1315 use rstest::rstest;
1316 use rust_decimal_macros::dec;
1317 use serde_json::json;
1318
1319 use super::*;
1320
1321 #[rstest]
1322 fn test_deserialize_authenticate_response() {
1323 let json = include_str!("../../test_data/http_authenticate.json");
1324 let response: AxAuthenticateResponse = serde_json::from_str(json).unwrap();
1325 assert!(response.token.starts_with("test-token"));
1326 }
1327
1328 #[rstest]
1329 fn test_serialize_cancel_all_orders_request() {
1330 let request = CancelAllOrdersRequest::new()
1331 .with_account_id(Ustr::from("account-1"))
1332 .with_symbol(Ustr::from("XAU-PERP"));
1333
1334 let value = serde_json::to_value(request).unwrap();
1335
1336 assert_eq!(value["account_id"], "account-1");
1337 assert_eq!(value["symbol"], "XAU-PERP");
1338 assert!(value.get("execution_venue").is_none());
1339 }
1340
1341 #[rstest]
1342 fn test_deserialize_whoami_response() {
1343 let json = include_str!("../../test_data/http_get_whoami.json");
1344
1345 let response: AxWhoAmI = serde_json::from_str(json).unwrap();
1346
1347 assert_eq!(response.id, "01JBXR-7QK2-0000");
1348 assert_eq!(response.username, "trader@example.com");
1349 assert_eq!(response.pseudonym.as_deref(), Some("quiet-amber-heron"));
1350 assert_eq!(
1351 response.created_at,
1352 "2025-12-18T02:20:42.675817Z".parse::<Timestamp>().unwrap()
1353 );
1354 assert!(!response.require_2fa);
1355 assert!(response.is_onboarded);
1356 assert!(!response.is_frozen);
1357 assert!(!response.is_admin);
1358 assert_eq!(
1359 response.fiat_deposit_code.as_deref(),
1360 Some("01JBXR7QK20000Y")
1361 );
1362 assert_eq!(response.accounts.len(), 1);
1363
1364 let account = &response.accounts[0];
1365
1366 assert_eq!(account.id, "01JBXR-7QK2-0000");
1367 assert_eq!(account.name, "trader@example.com");
1368 assert!(!account.is_close_only);
1369 assert_eq!(account.maker_fee, Some(dec!(0.0002)));
1370 assert_eq!(account.taker_fee, Some(dec!(0.0025)));
1371 assert!(account.can_list);
1372 assert!(account.can_read);
1373 assert!(account.can_set_limits);
1374 assert!(account.can_reduce_or_close);
1375 assert!(account.can_trade);
1376 }
1377
1378 #[rstest]
1379 #[case(json!(""), None)]
1380 #[case(json!(null), None)]
1381 #[case(json!("0"), Some(Decimal::ZERO))]
1382 #[case(json!("0.0002"), Some(dec!(0.0002)))]
1383 fn test_deserialize_whoami_account_fee_distinguishes_absent_from_zero(
1384 #[case] wire_value: serde_json::Value,
1385 #[case] expected: Option<Decimal>,
1386 ) {
1387 let json = json!({
1389 "id": "01JBXR-7QK2-0000",
1390 "name": "trader@example.com",
1391 "is_close_only": false,
1392 "maker_fee": wire_value,
1393 "taker_fee": wire_value,
1394 "can_list": true,
1395 "can_read": true,
1396 "can_set_limits": true,
1397 "can_reduce_or_close": true,
1398 "can_trade": true,
1399 })
1400 .to_string();
1401
1402 let account: AxWhoAmIAccount = serde_json::from_str(&json).unwrap();
1403
1404 assert_eq!(account.maker_fee, expected);
1405 assert_eq!(account.taker_fee, expected);
1406 }
1407
1408 #[rstest]
1409 fn test_deserialize_whoami_account_rejects_malformed_fee() {
1410 let json = json!({
1411 "id": "01JBXR-7QK2-0000",
1412 "name": "trader@example.com",
1413 "is_close_only": false,
1414 "maker_fee": "not-a-decimal",
1415 "taker_fee": "0.0025",
1416 "can_list": true,
1417 "can_read": true,
1418 "can_set_limits": true,
1419 "can_reduce_or_close": true,
1420 "can_trade": true,
1421 })
1422 .to_string();
1423
1424 let error = serde_json::from_str::<AxWhoAmIAccount>(&json).unwrap_err();
1425
1426 assert!(
1427 error.to_string().contains("Invalid decimal"),
1428 "unexpected error: {error}"
1429 );
1430 }
1431
1432 #[rstest]
1433 fn test_deserialize_whoami_response_without_optional_profile_fields() {
1434 let json = json!({
1435 "id": "01JBXR-7QK2-0001",
1436 "username": "sub@example.com",
1437 "created_at": "2025-12-18T02:20:42.675817Z",
1438 "is_onboarded": true,
1439 "is_frozen": false,
1440 "is_admin": false,
1441 "require_2fa": true,
1442 "accounts": [],
1443 })
1444 .to_string();
1445
1446 let response: AxWhoAmI = serde_json::from_str(&json).unwrap();
1447
1448 assert!(response.require_2fa);
1449 assert_eq!(response.pseudonym, None);
1450 assert_eq!(response.fiat_deposit_code, None);
1451 assert!(response.accounts.is_empty());
1452 }
1453
1454 #[rstest]
1455 fn test_deserialize_instruments_response() {
1456 let json = include_str!("../../test_data/http_get_instruments.json");
1457 let response: AxInstrumentsResponse = serde_json::from_str(json).unwrap();
1458 assert_eq!(response.instruments.len(), 3);
1459 assert_eq!(response.instruments[0].symbol, "EURUSD-PERP");
1460 }
1461
1462 #[rstest]
1463 fn test_deserialize_balances_response() {
1464 let json = include_str!("../../test_data/http_get_balances.json");
1465 let response: AxBalancesResponse = serde_json::from_str(json).unwrap();
1466 assert_eq!(response.balances.len(), 3);
1467 assert_eq!(response.balances[0].symbol, "USD");
1468 }
1469
1470 #[rstest]
1471 fn test_deserialize_positions_response() {
1472 let json = include_str!("../../test_data/http_get_positions.json");
1473 let response: AxPositionsResponse = serde_json::from_str(json).unwrap();
1474 assert_eq!(response.positions.len(), 2);
1475 assert_eq!(response.positions[0].symbol, "BTC-PERP");
1476 assert_eq!(response.positions[1].signed_quantity, -5);
1477 }
1478
1479 #[rstest]
1480 fn test_deserialize_tickers_response() {
1481 let json = include_str!("../../test_data/http_get_tickers.json");
1482 let response: AxTickersResponse = serde_json::from_str(json).unwrap();
1483 assert_eq!(response.tickers.len(), 3);
1484 assert_eq!(response.total_count, 3);
1485 assert_eq!(response.limit, 100);
1486 assert_eq!(response.offset, 0);
1487 assert_eq!(response.tickers[0].symbol, "EURUSD-PERP");
1488 assert!(response.tickers[0].bid.is_some());
1489 assert!(response.tickers[2].bid.is_none());
1490 }
1491
1492 #[rstest]
1493 fn test_deserialize_funding_rates_response() {
1494 let json = include_str!("../../test_data/http_get_funding_rates.json");
1495 let response: AxFundingRatesResponse = serde_json::from_str(json).unwrap();
1496 assert_eq!(response.funding_rates.len(), 2);
1497 assert_eq!(response.funding_rates[0].symbol, "JPYUSD-PERP");
1498 }
1499
1500 #[rstest]
1501 fn test_deserialize_funding_slots_response() {
1502 let json = include_str!("../../test_data/http_get_funding_slots.json");
1503 let response: AxFundingSlotsResponse = serde_json::from_str(json).unwrap();
1504 assert_eq!(response.symbol, "EURUSD-PERP");
1505 assert_eq!(response.date, Date::new(2026, 7, 6).unwrap());
1506 assert_eq!(response.timezone, "America/New_York");
1507 assert_eq!(response.variant, AxFundingVariant::IntradayTwap);
1508 assert_eq!(response.interval_count, 4);
1509 assert_eq!(
1510 response.cap_bps.map(|d| d.to_string()),
1511 Some("5.0".to_string())
1512 );
1513 assert_eq!(response.slots.len(), 4);
1514
1515 let first = &response.slots[0];
1516 assert_eq!(first.index, 1);
1517 assert_eq!(first.status, AxFundingSlotStatus::Realized);
1518 assert!(!first.capped);
1519 assert_eq!(
1520 first.funding_rate_bps.map(|d| d.to_string()),
1521 Some("0.0921".to_string())
1522 );
1523 assert!(first.reason.is_none());
1524
1525 let capped = &response.slots[1];
1526 assert!(capped.capped);
1527 assert_eq!(
1528 capped.funding_rate_bps.map(|d| d.to_string()),
1529 Some("5.0000".to_string())
1530 );
1531
1532 let projected = &response.slots[2];
1533 assert_eq!(projected.status, AxFundingSlotStatus::Projected);
1534
1535 let skipped = &response.slots[3];
1536 assert_eq!(skipped.status, AxFundingSlotStatus::Skipped);
1537 assert!(skipped.mark_twap.is_none());
1538 assert!(skipped.funding_rate_bps.is_none());
1539 assert_eq!(skipped.reason.as_deref(), Some("holiday"));
1540
1541 assert_eq!(response.realized_sum_bps.to_string(), "5.0921");
1542 assert_eq!(response.projected_eod_bps.to_string(), "5.1842");
1543 }
1544
1545 #[rstest]
1546 fn test_funding_variant_and_slot_status_deserialization() {
1547 let daily: AxFundingVariant =
1548 serde_json::from_value(serde_json::json!("daily_close")).unwrap();
1549 let twap: AxFundingVariant =
1550 serde_json::from_value(serde_json::json!("intraday_twap")).unwrap();
1551 assert_eq!(daily, AxFundingVariant::DailyClose);
1552 assert_eq!(twap, AxFundingVariant::IntradayTwap);
1553
1554 let statuses = [
1555 ("realized", AxFundingSlotStatus::Realized),
1556 ("projected", AxFundingSlotStatus::Projected),
1557 ("skipped", AxFundingSlotStatus::Skipped),
1558 ("pending", AxFundingSlotStatus::Pending),
1559 ];
1560
1561 for (raw, expected) in statuses {
1562 let parsed: AxFundingSlotStatus =
1563 serde_json::from_value(serde_json::json!(raw)).unwrap();
1564 assert_eq!(parsed, expected);
1565 }
1566 }
1567
1568 #[rstest]
1569 fn test_deserialize_open_orders_response() {
1570 let json = include_str!("../../test_data/http_get_open_orders.json");
1571 let response: AxOpenOrdersResponse = serde_json::from_str(json).unwrap();
1572 assert_eq!(response.orders.len(), 2);
1573 assert_eq!(response.orders[0].oid, "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1574 assert_eq!(response.orders[0].d, AxOrderSide::Buy);
1575 assert_eq!(response.orders[0].o, AxOrderStatus::Accepted);
1576 assert_eq!(response.orders[1].xq, 300);
1577 assert_eq!(response.total_count, 2);
1578 assert_eq!(response.limit, 100);
1579 assert_eq!(response.offset, 0);
1580 }
1581
1582 #[rstest]
1583 fn test_deserialize_fills_response() {
1584 let json = include_str!("../../test_data/http_get_fills.json");
1585 let response: AxFillsResponse = serde_json::from_str(json).unwrap();
1586 assert_eq!(response.fills.len(), 2);
1587 assert_eq!(response.fills[0].side, AxOrderSide::Buy);
1588 assert!(response.fills[0].is_taker);
1589 assert!(!response.fills[1].is_taker);
1590 assert_eq!(response.fills[0].is_block_trade, Some(false));
1591 assert_eq!(response.fills[0].is_final_settlement, Some(false));
1592 assert_eq!(response.total_count, Some(2));
1593 assert_eq!(response.limit, Some(100));
1594 assert_eq!(response.next_cursor, None);
1595 }
1596
1597 #[rstest]
1598 fn test_deserialize_candles_response() {
1599 let json = include_str!("../../test_data/http_get_candles.json");
1600 let response: AxCandlesResponse = serde_json::from_str(json).unwrap();
1601 assert_eq!(response.candles.len(), 2);
1602 assert_eq!(response.candles[0].symbol, "EURUSD-PERP");
1603 assert_eq!(response.candles[0].width, AxCandleWidth::Minutes1);
1604 }
1605
1606 #[rstest]
1607 fn test_deserialize_candle_response() {
1608 let json = include_str!("../../test_data/http_get_candle.json");
1609 let response: AxCandleResponse = serde_json::from_str(json).unwrap();
1610 assert_eq!(response.candle.symbol, "EURUSD-PERP");
1611 assert_eq!(response.candle.width, AxCandleWidth::Minutes1);
1612 }
1613
1614 #[rstest]
1615 fn test_deserialize_risk_snapshot_response() {
1616 let json = include_str!("../../test_data/http_get_risk_snapshot.json");
1617 let response: AxRiskSnapshotResponse = serde_json::from_str(json).unwrap();
1618 assert_eq!(
1619 response.risk_snapshot.account_id,
1620 Ustr::from("3c90c3cc-0d44-4b50-8888-8dd25736052a")
1621 );
1622 assert_eq!(response.risk_snapshot.per_symbol.len(), 2);
1623 assert!(
1624 response
1625 .risk_snapshot
1626 .per_symbol
1627 .contains_key("EURUSD-PERP")
1628 );
1629 assert_eq!(
1630 response.risk_snapshot.per_symbol["GBPUSD-PERP"].average_price,
1631 None
1632 );
1633 }
1634
1635 #[rstest]
1636 fn test_deserialize_transactions_response() {
1637 let json = include_str!("../../test_data/http_get_transactions.json");
1638 let response: AxTransactionsResponse = serde_json::from_str(json).unwrap();
1639 assert_eq!(response.transactions.len(), 2);
1640 assert_eq!(response.total_count, Some(2));
1641 assert_eq!(response.limit, Some(100));
1642 assert_eq!(response.transactions[0].account_id, Ustr::from("account-1"));
1643 assert_eq!(response.transactions[0].transaction_type, "deposit");
1644 assert!(response.transactions[0].initiated_by_user_id.is_some());
1645 assert!(response.transactions[1].reference_id.is_none());
1646 }
1647
1648 #[rstest]
1649 fn test_deserialize_preview_aggressive_limit_order_response() {
1650 let json = include_str!("../../test_data/http_preview_aggressive_limit_order.json");
1651 let response: AxPreviewAggressiveLimitOrderResponse = serde_json::from_str(json).unwrap();
1652 assert_eq!(response.filled_quantity, 1000);
1653 assert_eq!(response.remaining_quantity, 0);
1654 assert!(response.limit_price.is_some());
1655 assert!(response.vwap.is_some());
1656 }
1657
1658 #[rstest]
1659 fn test_deserialize_place_order_response() {
1660 let json = include_str!("../../test_data/http_place_order.json");
1661 let response: AxPlaceOrderResponse = serde_json::from_str(json).unwrap();
1662 assert_eq!(response.oid, "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1663 }
1664
1665 #[rstest]
1666 fn test_deserialize_cancel_order_response() {
1667 let json = include_str!("../../test_data/http_cancel_order.json");
1668 let response: AxCancelOrderResponse = serde_json::from_str(json).unwrap();
1669 assert!(response.cxl_rx);
1670 }
1671
1672 #[rstest]
1673 fn test_deserialize_cancel_all_orders_response() {
1674 let json = include_str!("../../test_data/http_cancel_all_orders.json");
1675 let _response: AxCancelAllOrdersResponse = serde_json::from_str(json).unwrap();
1676 }
1677
1678 #[rstest]
1679 fn test_deserialize_trades_response() {
1680 let json = include_str!("../../test_data/http_get_trades.json");
1681 let response: AxTradesResponse = serde_json::from_str(json).unwrap();
1682 assert_eq!(response.trades.len(), 2);
1683 assert_eq!(response.trades[0].s, "EURUSD-PERP");
1684 assert_eq!(response.trades[0].d, AxOrderSide::Buy);
1685 assert_eq!(response.trades[0].q, 100);
1686 assert_eq!(response.trades[1].d, AxOrderSide::Sell);
1687 }
1688
1689 #[rstest]
1690 fn test_deserialize_book_response() {
1691 let json = include_str!("../../test_data/http_get_book.json");
1692 let response: AxBookResponse = serde_json::from_str(json).unwrap();
1693 assert_eq!(response.book.s, "EURUSD-PERP");
1694 assert_eq!(response.book.b.len(), 3);
1695 assert_eq!(response.book.a.len(), 3);
1696 assert_eq!(response.book.b[0].q, 500);
1697 assert_eq!(response.book.a[0].q, 400);
1698 }
1699
1700 #[rstest]
1701 fn test_deserialize_order_status_query_response() {
1702 let json = include_str!("../../test_data/http_get_order_status.json");
1703 let response: AxOrderStatusQueryResponse = serde_json::from_str(json).unwrap();
1704 assert_eq!(response.status.symbol, "EURUSD-PERP");
1705 assert_eq!(response.status.order_id, "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1706 assert_eq!(response.status.state, AxOrderStatus::PartiallyFilled);
1707 assert_eq!(response.status.clord_id, Some(12345));
1708 assert_eq!(response.status.filled_quantity, Some(300));
1709 assert_eq!(response.status.remaining_quantity, Some(700));
1710 assert_eq!(response.status.reject_reason, None);
1711 assert_eq!(response.status.reject_message, None);
1712 }
1713
1714 #[rstest]
1715 fn test_deserialize_orders_response() {
1716 let json = include_str!("../../test_data/http_get_orders.json");
1717 let response: AxOrdersResponse = serde_json::from_str(json).unwrap();
1718 assert_eq!(response.orders.len(), 2);
1719 assert_eq!(response.total_count, Some(2));
1720 assert_eq!(response.limit, Some(100));
1721 assert_eq!(response.next_cursor, None);
1722 assert_eq!(response.orders[0].aid.as_deref(), Some("account-1"));
1723 assert_eq!(response.orders[0].o, AxOrderStatus::PartiallyFilled);
1724 assert_eq!(response.orders[0].xq, 300);
1725 assert_eq!(response.orders[1].o, AxOrderStatus::Filled);
1726 assert_eq!(response.orders[1].d, AxOrderSide::Sell);
1727 }
1728
1729 #[rstest]
1730 fn test_deserialize_initial_margin_requirement_response() {
1731 let json = include_str!("../../test_data/http_initial_margin_requirement.json");
1732 let response: AxInitialMarginRequirementResponse = serde_json::from_str(json).unwrap();
1733 assert_eq!(response.im, Decimal::new(125050, 2));
1734 }
1735
1736 #[rstest]
1737 fn test_deserialize_replace_order_response() {
1738 let json = include_str!("../../test_data/http_replace_order.json");
1739 let response: AxReplaceOrderResponse = serde_json::from_str(json).unwrap();
1740 assert_eq!(response.oid, "O-01ARZ3NDEKTSV4RRFFQ69G5NEW");
1741 }
1742
1743 #[rstest]
1744 fn test_replace_order_request_serialization() {
1745 let request = ReplaceOrderRequest::new("O-01ARZ3NDEKTSV4RRFFQ69G5FAV")
1746 .with_price(Decimal::new(10550, 4))
1747 .with_quantity(200);
1748
1749 let json = serde_json::to_value(&request).unwrap();
1750 assert_eq!(json["oid"], "O-01ARZ3NDEKTSV4RRFFQ69G5FAV");
1751 assert_eq!(json["p"], "1.0550");
1752 assert_eq!(json["q"], 200);
1753 assert!(json.get("po").is_none());
1754 assert!(json.get("tif").is_none());
1755 assert!(json.get("trigger_price").is_none());
1756 }
1757
1758 #[rstest]
1759 fn test_replace_order_request_minimal() {
1760 let request = ReplaceOrderRequest::new("O-TEST");
1761 let json = serde_json::to_value(&request).unwrap();
1762 assert_eq!(json["oid"], "O-TEST");
1763 assert!(json.get("p").is_none());
1764 assert!(json.get("q").is_none());
1765 }
1766}