brokapi_bcs_protocol/ws/parser.rs
1use tracing::warn;
2
3use crate::models::messages::IncomingMessage;
4use crate::ws::client::BcsWsChannel;
5use crate::ws::models::{MarketDataMessage, WsRawMessage};
6
7/// Parses one text frame of a channel into an [`IncomingMessage`].
8///
9/// The function never fails and never panics. The order of tries is:
10///
11/// 1. A frame with `"type": "error"` becomes [`IncomingMessage::Error`].
12/// 2. A frame with a `responseType` becomes [`IncomingMessage::MarketData`].
13/// 3. Otherwise the frame is read as the data type of `service`: limits, portfolio, order message
14/// (execution and events channels), transaction message, margin indicators, or market data.
15/// 4. A frame that fits none of these comes back as [`IncomingMessage::Unknown`] with the raw text.
16/// A broken frame on the transaction channel is also logged as a warning, without its text (it holds account data).
17///
18/// A session calls this for every text frame. Call it yourself to test your handler on a sample frame.
19///
20/// # Examples
21///
22/// ```
23/// use brokapi_bcs_protocol::models::enums::OrderStatus;
24/// use brokapi_bcs_protocol::models::messages::IncomingMessage;
25/// use brokapi_bcs_protocol::ws::client::BcsWsChannel;
26/// use brokapi_bcs_protocol::ws::parse_incoming;
27///
28/// // An order event: the order is filled.
29/// let frame = r#"{
30/// "originalClientOrderId": "550e8400-e29b-41d4-a716-446655440000",
31/// "clientOrderId": "",
32/// "data": {"orderStatus": "2", "executionType": "2", "ticker": "SBER", "classCode": "TQBR",
33/// "side": "1", "orderType": "2", "orderQuantity": 1, "executedQuantity": 1,
34/// "lastQuantity": 1, "remainedQuantity": 0, "price": 300.0}
35/// }"#;
36/// match parse_incoming(frame, BcsWsChannel::OrderEvents) {
37/// IncomingMessage::OrderExecution(m) => {
38/// assert_eq!(m.data.unwrap().order_status, OrderStatus::Filled);
39/// }
40/// other => panic!("unexpected: {other:?}"),
41/// }
42///
43/// // Text that is not a frame of the broker is kept, not dropped.
44/// assert!(matches!(parse_incoming("hello", BcsWsChannel::Limits), IncomingMessage::Unknown(_)));
45/// ```
46pub fn parse_incoming(text: &str, service: BcsWsChannel) -> IncomingMessage {
47 // Сначала определяем тип сообщения
48 if let Ok(raw) = serde_json::from_str::<WsRawMessage>(text) {
49 // Error
50 if raw.msg_type.as_deref() == Some("error")
51 && let Ok(err) = serde_json::from_str(text)
52 {
53 return IncomingMessage::Error(err);
54 }
55 // Market data (имеет responseType)
56 if raw.response_type.is_some()
57 && let Ok(md) = serde_json::from_str::<MarketDataMessage>(text)
58 {
59 return IncomingMessage::MarketData(md);
60 }
61 }
62
63 // По типу сервиса
64 match service {
65 BcsWsChannel::Limits => {
66 if let Ok(data) = serde_json::from_str(text) {
67 return IncomingMessage::Limits(data);
68 }
69 }
70 BcsWsChannel::Portfolio => {
71 if let Ok(data) = serde_json::from_str(text) {
72 return IncomingMessage::Portfolio(data);
73 }
74 }
75 BcsWsChannel::OrderExecution | BcsWsChannel::OrderEvents => {
76 if let Ok(data) = serde_json::from_str(text) {
77 return IncomingMessage::OrderExecution(data);
78 }
79 }
80 BcsWsChannel::OrderTransaction => match serde_json::from_str(text) {
81 Ok(data) => {
82 return IncomingMessage::OrderTransaction(data);
83 }
84 Err(e) => {
85 // Only the kind and the position of the failure: the frame holds account data and the
86 // message of serde can quote a value.
87 warn!(
88 "[WS_PARSE] OrderTransaction frame not understood: {:?} at line {} column {}, {} bytes",
89 e.classify(),
90 e.line(),
91 e.column(),
92 text.len()
93 );
94 }
95 },
96 BcsWsChannel::MarginalIndicators => {
97 if let Ok(data) = serde_json::from_str(text) {
98 return IncomingMessage::MarginalIndicators(data);
99 }
100 }
101 BcsWsChannel::MarketData => {
102 if let Ok(md) = serde_json::from_str::<MarketDataMessage>(text) {
103 return IncomingMessage::MarketData(md);
104 }
105 }
106 }
107
108 IncomingMessage::Unknown(text.to_string())
109}