1use std::collections::HashMap;
2
3use rust_decimal::Decimal;
4use serde::{Deserialize, Serialize};
5
6use crate::order::OrderStatus;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "camelCase")]
11#[non_exhaustive]
12pub struct OrderResponse {
13 pub order_id: String,
15 pub filled_price: Option<Decimal>,
17 pub filled_size: Decimal,
19 pub requested_size: Decimal,
21 pub status: OrderStatus,
23}
24
25impl OrderResponse {
26 pub fn new(
28 order_id: String,
29 filled_price: Option<Decimal>,
30 filled_size: Decimal,
31 requested_size: Decimal,
32 status: OrderStatus,
33 ) -> Self {
34 Self {
35 order_id,
36 filled_price,
37 filled_size,
38 requested_size,
39 status,
40 }
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51#[non_exhaustive]
52pub struct HlActionResponse {
53 pub status: String,
55 #[serde(default)]
57 pub response: Option<serde_json::Value>,
58 #[serde(flatten)]
60 pub extra: HashMap<String, serde_json::Value>,
61}
62
63impl HlActionResponse {
64 pub fn is_ok(&self) -> bool {
66 self.status == "ok"
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use std::str::FromStr;
74
75 #[test]
76 fn order_response_serde_roundtrip() {
77 let resp = OrderResponse {
78 order_id: "abc123".into(),
79 filled_price: Some(Decimal::from_str("50000.0").unwrap()),
80 filled_size: Decimal::from_str("0.1").unwrap(),
81 requested_size: Decimal::from_str("0.1").unwrap(),
82 status: OrderStatus::Filled,
83 };
84 let json = serde_json::to_string(&resp).unwrap();
85 let parsed: OrderResponse = serde_json::from_str(&json).unwrap();
86 assert_eq!(parsed.order_id, "abc123");
87 assert_eq!(
88 parsed.filled_price,
89 Some(Decimal::from_str("50000.0").unwrap())
90 );
91 assert_eq!(parsed.filled_size, Decimal::from_str("0.1").unwrap());
92 assert_eq!(parsed.requested_size, Decimal::from_str("0.1").unwrap());
93 assert_eq!(parsed.status, OrderStatus::Filled);
94 }
95
96 #[test]
97 fn order_response_no_fill_price_roundtrip() {
98 let resp = OrderResponse {
99 order_id: "xyz".into(),
100 filled_price: None,
101 filled_size: Decimal::ZERO,
102 requested_size: Decimal::ONE,
103 status: OrderStatus::Open,
104 };
105 let json = serde_json::to_string(&resp).unwrap();
106 let parsed: OrderResponse = serde_json::from_str(&json).unwrap();
107 assert!(parsed.filled_price.is_none());
108 assert_eq!(parsed.status, OrderStatus::Open);
109 }
110
111 #[test]
112 fn order_response_camel_case_keys() {
113 let resp = OrderResponse {
114 order_id: "x".into(),
115 filled_price: None,
116 filled_size: Decimal::ZERO,
117 requested_size: Decimal::ZERO,
118 status: OrderStatus::Open,
119 };
120 let json = serde_json::to_string(&resp).unwrap();
121 assert!(json.contains("orderId"));
122 assert!(json.contains("filledPrice"));
123 assert!(json.contains("filledSize"));
124 assert!(json.contains("requestedSize"));
125 }
126
127 #[test]
128 fn action_response_ok_roundtrip() {
129 let json = serde_json::json!({
130 "status": "ok",
131 "response": {"type": "cancel", "data": {"statuses": ["success"]}}
132 });
133 let parsed: HlActionResponse = serde_json::from_value(json).unwrap();
134 assert_eq!(parsed.status, "ok");
135 assert!(parsed.is_ok());
136 assert!(parsed.response.is_some());
137 }
138
139 #[test]
140 fn action_response_error() {
141 let json = serde_json::json!({
142 "status": "err",
143 "response": "Order not found"
144 });
145 let parsed: HlActionResponse = serde_json::from_value(json).unwrap();
146 assert_eq!(parsed.status, "err");
147 assert!(!parsed.is_ok());
148 }
149
150 #[test]
151 fn action_response_no_response_field() {
152 let json = serde_json::json!({ "status": "ok" });
153 let parsed: HlActionResponse = serde_json::from_value(json).unwrap();
154 assert!(parsed.is_ok());
155 assert!(parsed.response.is_none());
156 }
157
158 #[test]
159 fn action_response_extra_fields_captured() {
160 let json = serde_json::json!({
161 "status": "ok",
162 "timestamp": 1700000000
163 });
164 let parsed: HlActionResponse = serde_json::from_value(json).unwrap();
165 assert!(parsed.extra.contains_key("timestamp"));
166 }
167
168 #[test]
169 fn action_response_serde_roundtrip() {
170 let resp = HlActionResponse {
171 status: "ok".into(),
172 response: Some(serde_json::json!({"data": "test"})),
173 extra: HashMap::new(),
174 };
175 let json = serde_json::to_string(&resp).unwrap();
176 let parsed: HlActionResponse = serde_json::from_str(&json).unwrap();
177 assert_eq!(parsed.status, "ok");
178 assert!(parsed.response.is_some());
179 }
180}