diff --git a/src/ws/sub_structs.rs b/src/ws/sub_structs.rs index 4c3c94e5..ac414647 100644 --- a/src/ws/sub_structs.rs +++ b/src/ws/sub_structs.rs @@ -112,7 +112,7 @@ pub struct CandleData { pub volume: String, } -#[derive(Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct OrderUpdate { pub order: BasicOrder, @@ -120,7 +120,7 @@ pub struct OrderUpdate { pub status_timestamp: u64, } -#[derive(Deserialize, Clone, Debug)] +#[derive(Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct BasicOrder { pub coin: String, @@ -129,7 +129,21 @@ pub struct BasicOrder { pub sz: String, pub oid: u64, pub timestamp: u64, + #[serde(default)] + pub trigger_condition: String, + #[serde(default)] + pub is_trigger: bool, + #[serde(default)] + pub trigger_px: String, + #[serde(default)] + pub is_position_tpsl: bool, + #[serde(default)] + pub reduce_only: bool, + #[serde(default)] + pub order_type: String, pub orig_sz: String, + #[serde(default)] + pub tif: Option, pub cloid: Option, } @@ -351,3 +365,87 @@ pub struct BboData { pub time: u64, pub bbo: Vec>, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserializes_limit_order_update() { + let json = r#"{ + "order": { + "coin": "BTC", + "side": "B", + "limitPx": "90000.0", + "sz": "0.001", + "oid": 123, + "timestamp": 1700000000000, + "origSz": "0.001", + "cloid": null, + "triggerCondition": "N/A", + "isTrigger": false, + "triggerPx": "0.0", + "isPositionTpsl": false, + "reduceOnly": false, + "orderType": "Limit", + "tif": "Gtc" + }, + "status": "open", + "statusTimestamp": 1700000000000 + }"#; + + let update: OrderUpdate = serde_json::from_str(json).unwrap(); + assert!(!update.order.is_trigger); + assert_eq!(update.order.trigger_condition, "N/A"); + assert_eq!(update.order.trigger_px, "0.0"); + assert_eq!(update.order.tif.as_deref(), Some("Gtc")); + assert!(!update.order.reduce_only); + assert_eq!(update.order.order_type, "Limit"); + } + + #[test] + fn deserializes_stop_market_order() { + let json = r#"{ + "coin": "BTC", + "side": "A", + "limitPx": "101856.0", + "sz": "0.001", + "oid": 456, + "timestamp": 1700000000000, + "origSz": "0.001", + "triggerCondition": "Price below 101856.0", + "isTrigger": true, + "triggerPx": "101856.0", + "isPositionTpsl": false, + "reduceOnly": true, + "orderType": "Stop Market", + "tif": null + }"#; + + let order: BasicOrder = serde_json::from_str(json).unwrap(); + assert!(order.is_trigger); + assert_eq!(order.trigger_px, "101856.0"); + assert!(order.trigger_condition.contains("Price below")); + assert!(order.reduce_only); + assert_eq!(order.order_type, "Stop Market"); + assert_eq!(order.tif, None); + } + + #[test] + fn deserializes_legacy_order_without_trigger_fields() { + let json = r#"{ + "coin": "ETH", + "side": "B", + "limitPx": "3000.0", + "sz": "1.0", + "oid": 789, + "timestamp": 1700000000000, + "origSz": "1.0" + }"#; + + let order: BasicOrder = serde_json::from_str(json).unwrap(); + assert!(!order.is_trigger); + assert_eq!(order.trigger_px, ""); + assert_eq!(order.tif, None); + } +}