1use crate::HlError;
2use rust_decimal::Decimal;
3use serde::de::{self, MapAccess, Visitor};
4use serde::ser::SerializeMap;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use std::fmt;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum Side {
12 Buy,
14 Sell,
16}
17
18impl Side {
19 pub fn is_buy(self) -> bool {
21 matches!(self, Side::Buy)
22 }
23
24 pub fn from_is_buy(is_buy: bool) -> Self {
26 if is_buy {
27 Side::Buy
28 } else {
29 Side::Sell
30 }
31 }
32}
33
34impl fmt::Display for Side {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self {
37 Side::Buy => write!(f, "buy"),
38 Side::Sell => write!(f, "sell"),
39 }
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
47#[non_exhaustive]
48pub enum Tif {
49 Gtc,
51 Ioc,
53 Alo,
55}
56
57impl fmt::Display for Tif {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match self {
60 Tif::Gtc => write!(f, "Gtc"),
61 Tif::Ioc => write!(f, "Ioc"),
62 Tif::Alo => write!(f, "Alo"),
63 }
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
71#[serde(rename_all = "lowercase")]
72pub enum Tpsl {
73 Sl,
75 Tp,
77}
78
79impl fmt::Display for Tpsl {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 Tpsl::Sl => write!(f, "sl"),
83 Tpsl::Tp => write!(f, "tp"),
84 }
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
90#[serde(rename_all = "lowercase")]
91pub enum PositionSide {
92 Long,
94 Short,
96}
97
98impl fmt::Display for PositionSide {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 match self {
101 PositionSide::Long => write!(f, "long"),
102 PositionSide::Short => write!(f, "short"),
103 }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
109#[serde(rename_all = "snake_case")]
110#[non_exhaustive]
111pub enum OrderStatus {
112 Filled,
114 Partial,
116 Open,
118 Rejected,
120 TriggerSl,
122 TriggerTp,
124}
125
126impl fmt::Display for OrderStatus {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 match self {
129 OrderStatus::Filled => write!(f, "filled"),
130 OrderStatus::Partial => write!(f, "partial"),
131 OrderStatus::Open => write!(f, "open"),
132 OrderStatus::Rejected => write!(f, "rejected"),
133 OrderStatus::TriggerSl => write!(f, "trigger_sl"),
134 OrderStatus::TriggerTp => write!(f, "trigger_tp"),
135 }
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(rename_all = "camelCase")]
142#[non_exhaustive]
143pub struct OrderWire {
144 pub asset: u32,
146 pub is_buy: bool,
148 pub limit_px: String,
150 pub sz: String,
152 pub reduce_only: bool,
154 pub order_type: OrderTypeWire,
156 #[serde(skip_serializing_if = "Option::is_none")]
158 pub cloid: Option<String>,
159}
160
161#[derive(Debug, Clone)]
167pub struct OrderWireBuilder {
168 asset: u32,
169 is_buy: bool,
170 limit_px: String,
171 sz: String,
172 reduce_only: bool,
173 order_type: OrderTypeWire,
174 cloid: Option<String>,
175}
176
177impl OrderWireBuilder {
178 pub fn tif(mut self, tif: Tif) -> Self {
182 if let OrderTypeWire::Limit(ref mut limit) = self.order_type {
183 limit.tif = tif;
184 }
185 self
186 }
187
188 pub fn cloid(mut self, cloid: impl Into<String>) -> Self {
190 self.cloid = Some(cloid.into());
191 self
192 }
193
194 pub fn reduce_only(mut self, reduce_only: bool) -> Self {
196 self.reduce_only = reduce_only;
197 self
198 }
199
200 pub fn build(self) -> Result<OrderWire, HlError> {
202 let px: Decimal = self
203 .limit_px
204 .parse()
205 .map_err(|_| HlError::Parse(format!("invalid price: {}", self.limit_px)))?;
206 if px <= Decimal::ZERO {
207 return Err(HlError::Parse(format!(
208 "price must be positive, got: {}",
209 self.limit_px
210 )));
211 }
212 let sz: Decimal = self
213 .sz
214 .parse()
215 .map_err(|_| HlError::Parse(format!("invalid size: {}", self.sz)))?;
216 if sz <= Decimal::ZERO {
217 return Err(HlError::Parse(format!(
218 "size must be positive, got: {}",
219 self.sz
220 )));
221 }
222 Ok(OrderWire {
223 asset: self.asset,
224 is_buy: self.is_buy,
225 limit_px: self.limit_px,
226 sz: self.sz,
227 reduce_only: self.reduce_only,
228 order_type: self.order_type,
229 cloid: self.cloid,
230 })
231 }
232}
233
234impl OrderWire {
235 pub fn limit_buy(asset: u32, limit_px: Decimal, sz: Decimal) -> OrderWireBuilder {
256 OrderWireBuilder {
257 asset,
258 is_buy: true,
259 limit_px: limit_px.normalize().to_string(),
260 sz: sz.normalize().to_string(),
261 reduce_only: false,
262 order_type: OrderTypeWire::Limit(LimitOrderType { tif: Tif::Gtc }),
263 cloid: None,
264 }
265 }
266
267 pub fn limit_sell(asset: u32, limit_px: Decimal, sz: Decimal) -> OrderWireBuilder {
271 OrderWireBuilder {
272 asset,
273 is_buy: false,
274 limit_px: limit_px.normalize().to_string(),
275 sz: sz.normalize().to_string(),
276 reduce_only: false,
277 order_type: OrderTypeWire::Limit(LimitOrderType { tif: Tif::Gtc }),
278 cloid: None,
279 }
280 }
281
282 pub fn trigger_buy(
287 asset: u32,
288 trigger_px: Decimal,
289 sz: Decimal,
290 tpsl: Tpsl,
291 ) -> OrderWireBuilder {
292 let trigger_px_str = trigger_px.normalize().to_string();
293 OrderWireBuilder {
294 asset,
295 is_buy: true,
296 limit_px: trigger_px_str.clone(),
297 sz: sz.normalize().to_string(),
298 reduce_only: true,
299 order_type: OrderTypeWire::Trigger(TriggerOrderType {
300 trigger_px: trigger_px_str,
301 is_market: true,
302 tpsl,
303 }),
304 cloid: None,
305 }
306 }
307
308 pub fn trigger_sell(
313 asset: u32,
314 trigger_px: Decimal,
315 sz: Decimal,
316 tpsl: Tpsl,
317 ) -> OrderWireBuilder {
318 let trigger_px_str = trigger_px.normalize().to_string();
319 OrderWireBuilder {
320 asset,
321 is_buy: false,
322 limit_px: trigger_px_str.clone(),
323 sz: sz.normalize().to_string(),
324 reduce_only: true,
325 order_type: OrderTypeWire::Trigger(TriggerOrderType {
326 trigger_px: trigger_px_str,
327 is_market: true,
328 tpsl,
329 }),
330 cloid: None,
331 }
332 }
333}
334
335#[derive(Debug, Clone, PartialEq, Eq)]
341#[non_exhaustive]
342pub enum OrderTypeWire {
343 Limit(LimitOrderType),
345 Trigger(TriggerOrderType),
347}
348
349impl OrderTypeWire {
350 pub fn is_limit(&self) -> bool {
352 matches!(self, OrderTypeWire::Limit(_))
353 }
354
355 pub fn is_trigger(&self) -> bool {
357 matches!(self, OrderTypeWire::Trigger(_))
358 }
359}
360
361impl Serialize for OrderTypeWire {
362 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
363 where
364 S: Serializer,
365 {
366 let mut map = serializer.serialize_map(Some(1))?;
367 match self {
368 OrderTypeWire::Limit(limit) => {
369 map.serialize_entry("limit", limit)?;
370 }
371 OrderTypeWire::Trigger(trigger) => {
372 map.serialize_entry("trigger", trigger)?;
373 }
374 }
375 map.end()
376 }
377}
378
379impl<'de> Deserialize<'de> for OrderTypeWire {
380 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
381 where
382 D: Deserializer<'de>,
383 {
384 struct OrderTypeWireVisitor;
385
386 impl<'de> Visitor<'de> for OrderTypeWireVisitor {
387 type Value = OrderTypeWire;
388
389 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
390 formatter.write_str("a map with either a \"limit\" or \"trigger\" key")
391 }
392
393 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
394 where
395 A: MapAccess<'de>,
396 {
397 let key: String = map
398 .next_key()?
399 .ok_or_else(|| de::Error::custom("empty order type object"))?;
400 match key.as_str() {
401 "limit" => {
402 let limit: LimitOrderType = map.next_value()?;
403 Ok(OrderTypeWire::Limit(limit))
404 }
405 "trigger" => {
406 let trigger: TriggerOrderType = map.next_value()?;
407 Ok(OrderTypeWire::Trigger(trigger))
408 }
409 other => Err(de::Error::unknown_field(other, &["limit", "trigger"])),
410 }
411 }
412 }
413
414 deserializer.deserialize_map(OrderTypeWireVisitor)
415 }
416}
417
418#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
420pub struct LimitOrderType {
421 pub tif: Tif,
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
427#[serde(rename_all = "camelCase")]
428pub struct TriggerOrderType {
429 pub trigger_px: String,
431 pub is_market: bool,
433 pub tpsl: Tpsl,
435}
436
437#[derive(Debug, Clone, PartialEq, Eq)]
439#[non_exhaustive]
440pub struct CancelRequest {
441 pub asset: u32,
443 pub oid: u64,
445}
446
447impl CancelRequest {
448 pub fn new(asset: u32, oid: u64) -> Self {
450 Self { asset, oid }
451 }
452}
453
454#[derive(Debug, Clone, PartialEq, Eq)]
456#[non_exhaustive]
457pub struct CancelByCloidRequest {
458 pub asset: u32,
460 pub cloid: String,
462}
463
464impl CancelByCloidRequest {
465 pub fn new(asset: u32, cloid: impl Into<String>) -> Self {
467 Self {
468 asset,
469 cloid: cloid.into(),
470 }
471 }
472}
473
474#[derive(Debug, Clone, PartialEq, Eq)]
476#[non_exhaustive]
477pub struct ModifyRequest {
478 pub oid: u64,
480 pub order: OrderWire,
482}
483
484impl ModifyRequest {
485 pub fn new(oid: u64, order: OrderWire) -> Self {
487 Self { oid, order }
488 }
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494 use std::str::FromStr;
495
496 #[test]
499 fn order_type_wire_limit_serialization() {
500 let ot = OrderTypeWire::Limit(LimitOrderType { tif: Tif::Gtc });
501 let json = serde_json::to_string(&ot).unwrap();
502 assert_eq!(json, r#"{"limit":{"tif":"Gtc"}}"#);
503 }
504
505 #[test]
506 fn order_type_wire_trigger_serialization() {
507 let ot = OrderTypeWire::Trigger(TriggerOrderType {
508 trigger_px: "99.0".into(),
509 is_market: true,
510 tpsl: Tpsl::Sl,
511 });
512 let json = serde_json::to_string(&ot).unwrap();
513 assert_eq!(
514 json,
515 r#"{"trigger":{"triggerPx":"99.0","isMarket":true,"tpsl":"sl"}}"#
516 );
517 }
518
519 #[test]
520 fn order_type_wire_limit_roundtrip() {
521 let original = OrderTypeWire::Limit(LimitOrderType { tif: Tif::Ioc });
522 let json = serde_json::to_string(&original).unwrap();
523 let parsed: OrderTypeWire = serde_json::from_str(&json).unwrap();
524 assert_eq!(parsed, original);
525 }
526
527 #[test]
528 fn order_type_wire_trigger_roundtrip() {
529 let original = OrderTypeWire::Trigger(TriggerOrderType {
530 trigger_px: "50.5".into(),
531 is_market: false,
532 tpsl: Tpsl::Tp,
533 });
534 let json = serde_json::to_string(&original).unwrap();
535 let parsed: OrderTypeWire = serde_json::from_str(&json).unwrap();
536 assert_eq!(parsed, original);
537 }
538
539 #[test]
540 fn order_type_wire_is_limit_and_is_trigger() {
541 let limit = OrderTypeWire::Limit(LimitOrderType { tif: Tif::Gtc });
542 assert!(limit.is_limit());
543 assert!(!limit.is_trigger());
544
545 let trigger = OrderTypeWire::Trigger(TriggerOrderType {
546 trigger_px: "1.0".into(),
547 is_market: true,
548 tpsl: Tpsl::Sl,
549 });
550 assert!(trigger.is_trigger());
551 assert!(!trigger.is_limit());
552 }
553
554 #[test]
555 fn order_type_wire_invalid_key_fails() {
556 let json = r#"{"unknown":{"tif":"Gtc"}}"#;
557 assert!(serde_json::from_str::<OrderTypeWire>(json).is_err());
558 }
559
560 #[test]
561 fn order_type_wire_empty_object_fails() {
562 let json = r#"{}"#;
563 assert!(serde_json::from_str::<OrderTypeWire>(json).is_err());
564 }
565
566 #[test]
569 fn builder_limit_buy_defaults() {
570 let order =
571 OrderWire::limit_buy(1, Decimal::from(90000), Decimal::from_str("0.001").unwrap())
572 .build()
573 .unwrap();
574 assert_eq!(order.asset, 1);
575 assert!(order.is_buy);
576 assert_eq!(order.limit_px, "90000");
577 assert_eq!(order.sz, "0.001");
578 assert!(!order.reduce_only);
579 assert!(order.order_type.is_limit());
580 assert!(order.cloid.is_none());
581 if let OrderTypeWire::Limit(ref l) = order.order_type {
582 assert_eq!(l.tif, Tif::Gtc);
583 }
584 }
585
586 #[test]
587 fn builder_limit_sell_with_options() {
588 let order = OrderWire::limit_sell(5, Decimal::from(3000), Decimal::from(2))
589 .tif(Tif::Ioc)
590 .cloid("my-order-1")
591 .reduce_only(true)
592 .build()
593 .unwrap();
594 assert_eq!(order.asset, 5);
595 assert!(!order.is_buy);
596 assert_eq!(order.limit_px, "3000");
597 assert_eq!(order.sz, "2");
598 assert!(order.reduce_only);
599 assert_eq!(order.cloid.as_deref(), Some("my-order-1"));
600 if let OrderTypeWire::Limit(ref l) = order.order_type {
601 assert_eq!(l.tif, Tif::Ioc);
602 } else {
603 panic!("expected limit order type");
604 }
605 }
606
607 #[test]
608 fn builder_trigger_buy() {
609 let order = OrderWire::trigger_buy(0, Decimal::from(99), Decimal::from(10), Tpsl::Sl)
610 .cloid("trigger-1")
611 .build()
612 .unwrap();
613 assert_eq!(order.asset, 0);
614 assert!(order.is_buy);
615 assert!(order.reduce_only);
616 assert!(order.order_type.is_trigger());
617 if let OrderTypeWire::Trigger(ref t) = order.order_type {
618 assert_eq!(t.trigger_px, "99");
619 assert!(t.is_market);
620 assert_eq!(t.tpsl, Tpsl::Sl);
621 } else {
622 panic!("expected trigger order type");
623 }
624 }
625
626 #[test]
627 fn builder_trigger_sell() {
628 let order = OrderWire::trigger_sell(2, Decimal::from(150), Decimal::from(5), Tpsl::Tp)
629 .reduce_only(false)
630 .build()
631 .unwrap();
632 assert_eq!(order.asset, 2);
633 assert!(!order.is_buy);
634 assert!(!order.reduce_only); assert!(order.order_type.is_trigger());
636 if let OrderTypeWire::Trigger(ref t) = order.order_type {
637 assert_eq!(t.trigger_px, "150");
638 assert_eq!(t.tpsl, Tpsl::Tp);
639 } else {
640 panic!("expected trigger order type");
641 }
642 }
643
644 #[test]
645 fn builder_tif_noop_on_trigger() {
646 let order = OrderWire::trigger_buy(0, Decimal::from(99), Decimal::ONE, Tpsl::Sl)
648 .tif(Tif::Ioc)
649 .build()
650 .unwrap();
651 assert!(order.order_type.is_trigger());
652 }
653
654 #[test]
655 fn build_validates_positive_price() {
656 let result = OrderWire::limit_buy(0, Decimal::ZERO, Decimal::ONE).build();
657 assert!(result.is_err());
658 }
659
660 #[test]
661 fn build_validates_positive_size() {
662 let result = OrderWire::limit_buy(0, Decimal::ONE, Decimal::ZERO).build();
663 assert!(result.is_err());
664 }
665
666 #[test]
667 fn build_validates_negative_price() {
668 let result = OrderWire::limit_buy(0, Decimal::from(-1), Decimal::ONE).build();
669 assert!(result.is_err());
670 }
671
672 #[test]
673 fn build_validates_negative_size() {
674 let result = OrderWire::limit_buy(0, Decimal::ONE, Decimal::from(-1)).build();
675 assert!(result.is_err());
676 }
677
678 #[test]
679 fn build_success() {
680 let result =
681 OrderWire::limit_buy(0, Decimal::from(90000), Decimal::from_str("0.001").unwrap())
682 .build();
683 assert!(result.is_ok());
684 }
685
686 #[test]
687 fn side_from_is_buy() {
688 assert_eq!(Side::from_is_buy(true), Side::Buy);
689 assert_eq!(Side::from_is_buy(false), Side::Sell);
690 }
691
692 #[test]
695 fn order_wire_limit_serde_roundtrip() {
696 let order =
697 OrderWire::limit_buy(1, Decimal::from(50000), Decimal::from_str("0.1").unwrap())
698 .cloid("test-cloid")
699 .build()
700 .unwrap();
701 let json = serde_json::to_string(&order).unwrap();
702 let parsed: OrderWire = serde_json::from_str(&json).unwrap();
703 assert_eq!(parsed.asset, 1);
704 assert!(parsed.is_buy);
705 assert_eq!(parsed.limit_px, "50000");
706 assert_eq!(parsed.sz, "0.1");
707 assert!(!parsed.reduce_only);
708 assert_eq!(parsed.cloid.as_deref(), Some("test-cloid"));
709 assert!(parsed.order_type.is_limit());
710 }
711
712 #[test]
713 fn order_wire_trigger_serde_roundtrip() {
714 let order = OrderWire::trigger_buy(0, Decimal::from(100), Decimal::from(10), Tpsl::Tp)
715 .build()
716 .unwrap();
717 let json = serde_json::to_string(&order).unwrap();
718 let parsed: OrderWire = serde_json::from_str(&json).unwrap();
719 let trigger = match parsed.order_type {
720 OrderTypeWire::Trigger(t) => t,
721 _ => panic!("expected trigger"),
722 };
723 assert_eq!(trigger.trigger_px, "100");
724 assert!(trigger.is_market);
725 assert_eq!(trigger.tpsl, Tpsl::Tp);
726 }
727
728 #[test]
729 fn order_wire_camel_case_serialization() {
730 let order = OrderWire::limit_buy(0, Decimal::ONE, Decimal::ONE)
731 .build()
732 .unwrap();
733 let json = serde_json::to_string(&order).unwrap();
734 assert!(json.contains("isBuy"));
735 assert!(json.contains("limitPx"));
736 assert!(json.contains("reduceOnly"));
737 assert!(json.contains("orderType"));
738 assert!(!json.contains("cloid"));
740 }
741
742 #[test]
743 fn order_wire_with_cloid_roundtrip() {
744 let order =
745 OrderWire::limit_sell(5, Decimal::from_str("3000.5").unwrap(), Decimal::from(2))
746 .reduce_only(true)
747 .cloid("my-order-123")
748 .build()
749 .unwrap();
750 let json = serde_json::to_string(&order).unwrap();
751 let parsed: OrderWire = serde_json::from_str(&json).unwrap();
752 assert_eq!(parsed.cloid.as_deref(), Some("my-order-123"));
753 assert!(parsed.reduce_only);
754 assert!(!parsed.is_buy);
755 }
756
757 #[test]
760 fn wire_format_limit_matches_hyperliquid() {
761 let ot = OrderTypeWire::Limit(LimitOrderType { tif: Tif::Gtc });
763 let json = serde_json::to_value(&ot).unwrap();
764 assert!(json.get("limit").is_some());
765 assert_eq!(json["limit"]["tif"], "Gtc");
766 }
767
768 #[test]
769 fn wire_format_trigger_matches_hyperliquid() {
770 let ot = OrderTypeWire::Trigger(TriggerOrderType {
772 trigger_px: "99.0".into(),
773 is_market: true,
774 tpsl: Tpsl::Sl,
775 });
776 let json = serde_json::to_value(&ot).unwrap();
777 assert!(json.get("trigger").is_some());
778 assert_eq!(json["trigger"]["triggerPx"], "99.0");
779 assert_eq!(json["trigger"]["isMarket"], true);
780 assert_eq!(json["trigger"]["tpsl"], "sl");
781 }
782
783 #[test]
784 fn deserialize_from_hyperliquid_limit_json() {
785 let json = r#"{"limit":{"tif":"Gtc"}}"#;
787 let ot: OrderTypeWire = serde_json::from_str(json).unwrap();
788 assert_eq!(ot, OrderTypeWire::Limit(LimitOrderType { tif: Tif::Gtc }));
789 }
790
791 #[test]
792 fn deserialize_from_hyperliquid_trigger_json() {
793 let json = r#"{"trigger":{"triggerPx":"99.0","isMarket":true,"tpsl":"sl"}}"#;
794 let ot: OrderTypeWire = serde_json::from_str(json).unwrap();
795 assert_eq!(
796 ot,
797 OrderTypeWire::Trigger(TriggerOrderType {
798 trigger_px: "99.0".into(),
799 is_market: true,
800 tpsl: Tpsl::Sl,
801 })
802 );
803 }
804
805 #[test]
808 fn tif_serde_wire_format() {
809 assert_eq!(serde_json::to_string(&Tif::Gtc).unwrap(), "\"Gtc\"");
810 assert_eq!(serde_json::to_string(&Tif::Ioc).unwrap(), "\"Ioc\"");
811 assert_eq!(serde_json::to_string(&Tif::Alo).unwrap(), "\"Alo\"");
812
813 assert_eq!(serde_json::from_str::<Tif>("\"Gtc\"").unwrap(), Tif::Gtc);
814 assert_eq!(serde_json::from_str::<Tif>("\"Ioc\"").unwrap(), Tif::Ioc);
815 assert_eq!(serde_json::from_str::<Tif>("\"Alo\"").unwrap(), Tif::Alo);
816 }
817
818 #[test]
819 fn tpsl_serde_wire_format() {
820 assert_eq!(serde_json::to_string(&Tpsl::Sl).unwrap(), "\"sl\"");
821 assert_eq!(serde_json::to_string(&Tpsl::Tp).unwrap(), "\"tp\"");
822
823 assert_eq!(serde_json::from_str::<Tpsl>("\"sl\"").unwrap(), Tpsl::Sl);
824 assert_eq!(serde_json::from_str::<Tpsl>("\"tp\"").unwrap(), Tpsl::Tp);
825 }
826
827 #[test]
828 fn side_serde_wire_format() {
829 assert_eq!(serde_json::to_string(&Side::Buy).unwrap(), "\"buy\"");
830 assert_eq!(serde_json::to_string(&Side::Sell).unwrap(), "\"sell\"");
831
832 assert_eq!(serde_json::from_str::<Side>("\"buy\"").unwrap(), Side::Buy);
833 assert_eq!(
834 serde_json::from_str::<Side>("\"sell\"").unwrap(),
835 Side::Sell
836 );
837 }
838
839 #[test]
840 fn side_is_buy() {
841 assert!(Side::Buy.is_buy());
842 assert!(!Side::Sell.is_buy());
843 }
844
845 #[test]
846 fn position_side_serde_wire_format() {
847 assert_eq!(
848 serde_json::to_string(&PositionSide::Long).unwrap(),
849 "\"long\""
850 );
851 assert_eq!(
852 serde_json::to_string(&PositionSide::Short).unwrap(),
853 "\"short\""
854 );
855
856 assert_eq!(
857 serde_json::from_str::<PositionSide>("\"long\"").unwrap(),
858 PositionSide::Long
859 );
860 assert_eq!(
861 serde_json::from_str::<PositionSide>("\"short\"").unwrap(),
862 PositionSide::Short
863 );
864 }
865
866 #[test]
867 fn order_status_serde_wire_format() {
868 assert_eq!(
869 serde_json::to_string(&OrderStatus::Filled).unwrap(),
870 "\"filled\""
871 );
872 assert_eq!(
873 serde_json::to_string(&OrderStatus::Partial).unwrap(),
874 "\"partial\""
875 );
876 assert_eq!(
877 serde_json::to_string(&OrderStatus::Open).unwrap(),
878 "\"open\""
879 );
880 assert_eq!(
881 serde_json::to_string(&OrderStatus::TriggerSl).unwrap(),
882 "\"trigger_sl\""
883 );
884 assert_eq!(
885 serde_json::to_string(&OrderStatus::TriggerTp).unwrap(),
886 "\"trigger_tp\""
887 );
888
889 assert_eq!(
890 serde_json::from_str::<OrderStatus>("\"filled\"").unwrap(),
891 OrderStatus::Filled
892 );
893 assert_eq!(
894 serde_json::from_str::<OrderStatus>("\"trigger_sl\"").unwrap(),
895 OrderStatus::TriggerSl
896 );
897 }
898
899 #[test]
900 fn display_impls() {
901 assert_eq!(Side::Buy.to_string(), "buy");
902 assert_eq!(Side::Sell.to_string(), "sell");
903 assert_eq!(Tif::Gtc.to_string(), "Gtc");
904 assert_eq!(Tpsl::Sl.to_string(), "sl");
905 assert_eq!(Tpsl::Tp.to_string(), "tp");
906 assert_eq!(PositionSide::Long.to_string(), "long");
907 assert_eq!(PositionSide::Short.to_string(), "short");
908 assert_eq!(OrderStatus::Filled.to_string(), "filled");
909 assert_eq!(OrderStatus::TriggerSl.to_string(), "trigger_sl");
910 }
911
912 #[test]
913 fn invalid_side_deserialization_fails() {
914 assert!(serde_json::from_str::<Side>("\"BUY\"").is_err());
915 assert!(serde_json::from_str::<Side>("\"Buy\"").is_err());
916 }
917
918 #[test]
919 fn invalid_tif_deserialization_fails() {
920 assert!(serde_json::from_str::<Tif>("\"gtc\"").is_err());
921 assert!(serde_json::from_str::<Tif>("\"GTC\"").is_err());
922 }
923}