nautilus_architect_ax/websocket/
parse.rs1use serde::de::Error;
23
24use super::{
25 error::AxWsErrorResponse,
26 messages::{AxMdErrorResponse, AxMdMessage, AxWsOrderResponse, AxWsRawMessage},
27};
28
29#[inline]
30fn peek_type_tag(bytes: &[u8]) -> Option<u8> {
31 if bytes.len() > 7
32 && bytes[0] == b'{'
33 && bytes[1] == b'"'
34 && bytes[2] == b't'
35 && bytes[3] == b'"'
36 && bytes[4] == b':'
37 && bytes[5] == b'"'
38 && bytes[7] == b'"'
39 {
40 Some(bytes[6])
41 } else {
42 None
43 }
44}
45
46#[inline]
47fn has_type_tag_prefix(bytes: &[u8]) -> bool {
48 bytes.len() > 5 && bytes[0] == b'{' && bytes[1] == b'"' && bytes[2] == b't' && bytes[3] == b'"'
49}
50
51pub fn parse_md_message(raw: &str) -> Result<AxMdMessage, serde_json::Error> {
61 if let Some(tag) = peek_type_tag(raw.as_bytes()) {
62 return match tag {
63 b'1' => serde_json::from_str(raw).map(AxMdMessage::BookL1),
64 b'2' => serde_json::from_str(raw).map(AxMdMessage::BookL2),
65 b'3' => serde_json::from_str(raw).map(AxMdMessage::BookL3),
66 b's' => serde_json::from_str(raw).map(AxMdMessage::Ticker),
67 b't' => serde_json::from_str(raw).map(AxMdMessage::Trade),
68 b'c' => serde_json::from_str(raw).map(AxMdMessage::Candle),
69 b'h' => serde_json::from_str(raw).map(AxMdMessage::Heartbeat),
70 b'e' => serde_json::from_str::<AxWsErrorResponse>(raw)
71 .map(|resp| AxMdMessage::Error(resp.into())),
72 tag => Err(serde_json::Error::custom(format!(
73 "unknown MD message type tag: '{}'",
74 tag as char
75 ))),
76 };
77 }
78
79 let value: serde_json::Value = serde_json::from_str(raw)?;
81
82 if value.get("result").is_some() {
83 return serde_json::from_value(value).map(AxMdMessage::SubscriptionResponse);
84 }
85
86 if value.get("error").is_some() {
87 return serde_json::from_value::<AxMdErrorResponse>(value)
88 .map(|resp| AxMdMessage::Error(resp.into()));
89 }
90
91 if let Some(t) = value.get("t").and_then(|v| v.as_str()) {
93 match t {
94 "1" => serde_json::from_value(value).map(AxMdMessage::BookL1),
95 "2" => serde_json::from_value(value).map(AxMdMessage::BookL2),
96 "3" => serde_json::from_value(value).map(AxMdMessage::BookL3),
97 "s" => serde_json::from_value(value).map(AxMdMessage::Ticker),
98 "t" => serde_json::from_value(value).map(AxMdMessage::Trade),
99 "c" => serde_json::from_value(value).map(AxMdMessage::Candle),
100 "h" => serde_json::from_value(value).map(AxMdMessage::Heartbeat),
101 "e" => serde_json::from_value::<AxWsErrorResponse>(value)
102 .map(|resp| AxMdMessage::Error(resp.into())),
103 other => Err(serde_json::Error::custom(format!(
104 "unknown MD message type: {other}"
105 ))),
106 }
107 } else {
108 Err(serde_json::Error::custom(
109 "MD message has no 't', 'result', or 'error' field",
110 ))
111 }
112}
113
114pub(crate) fn parse_order_message(raw: &str) -> Result<AxWsRawMessage, serde_json::Error> {
121 if has_type_tag_prefix(raw.as_bytes()) {
123 return serde_json::from_str(raw).map(|e| AxWsRawMessage::Event(Box::new(e)));
124 }
125
126 let value: serde_json::Value = serde_json::from_str(raw)?;
128
129 if value.get("err").is_some() {
130 return serde_json::from_value(value).map(AxWsRawMessage::Error);
131 }
132
133 if let Some(res) = value.get("res") {
134 if res.is_array() {
135 return serde_json::from_value(value)
136 .map(|r| AxWsRawMessage::Response(AxWsOrderResponse::OpenOrders(r)));
137 }
138 if res.get("oid").is_some() {
139 return serde_json::from_value(value)
140 .map(|r| AxWsRawMessage::Response(AxWsOrderResponse::PlaceOrder(r)));
141 }
142 if res.get("cxl_rx").is_some() {
143 return serde_json::from_value(value)
144 .map(|r| AxWsRawMessage::Response(AxWsOrderResponse::CancelOrder(r)));
145 }
146 if res.get("li").is_some() {
147 return serde_json::from_value(value)
148 .map(|r| AxWsRawMessage::Response(AxWsOrderResponse::List(r)));
149 }
150
151 return Err(serde_json::Error::custom(
152 "unrecognized order response shape",
153 ));
154 }
155
156 if value.get("t").is_some() {
158 return serde_json::from_value(value).map(|e| AxWsRawMessage::Event(Box::new(e)));
159 }
160
161 Err(serde_json::Error::custom(
162 "order WS message has no 't', 'err', or 'res' field",
163 ))
164}