Skip to main content

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}