deribit_websocket/message/builder.rs
1//! Aggregate message builder and parser for JSON-RPC traffic.
2//!
3//! Combines the three stateless helpers in [`crate::message`] into a
4//! single facade: [`RequestBuilder`] for *outbound* JSON-RPC requests,
5//! [`ResponseHandler`] for *inbound* replies, and [`NotificationHandler`]
6//! for server-initiated pushes. Typical usage is to hold one
7//! [`MessageBuilder`] per connection and feed every frame read off the
8//! wire to [`MessageBuilder::parse_message`], dispatching on the
9//! returned [`MessageType`].
10
11use crate::message::{NotificationHandler, RequestBuilder, ResponseHandler};
12use crate::model::ws_types::{JsonRpcNotification, JsonRpcRequest, JsonRpcResponse};
13
14/// Aggregate JSON-RPC message builder/parser.
15///
16/// Holds the three sub-components needed to drive a JSON-RPC session:
17///
18/// - [`RequestBuilder`] — produces outbound requests and assigns
19/// monotonically-increasing ids; held mutably because it mutates
20/// that id counter on every call.
21/// - [`ResponseHandler`] — parses inbound replies keyed by id; fully
22/// stateless, so a shared reference is enough.
23/// - [`NotificationHandler`] — parses server-initiated pushes
24/// (subscription events, heartbeats); also stateless.
25///
26/// One instance per WebSocket connection is the intended lifetime: the
27/// id counter must not be shared across connections, and the handlers
28/// hold no connection-specific state.
29#[derive(Debug)]
30pub struct MessageBuilder {
31 request_builder: RequestBuilder,
32 response_handler: ResponseHandler,
33 notification_handler: NotificationHandler,
34}
35
36impl Default for MessageBuilder {
37 fn default() -> Self {
38 Self::new()
39 }
40}
41
42impl MessageBuilder {
43 /// Create a new aggregate message builder with a fresh id counter
44 /// and stateless response/notification parsers.
45 #[must_use]
46 pub fn new() -> Self {
47 Self {
48 request_builder: RequestBuilder::new(),
49 response_handler: ResponseHandler::new(),
50 notification_handler: NotificationHandler::new(),
51 }
52 }
53
54 /// Borrow the request builder mutably.
55 ///
56 /// Mutability is required because every outbound request advances
57 /// the builder's id counter. Prefer calling this from a single
58 /// task: the `MessageBuilder` itself is not `Sync`-protected.
59 pub fn request_builder(&mut self) -> &mut RequestBuilder {
60 &mut self.request_builder
61 }
62
63 /// Borrow the response handler.
64 ///
65 /// Returned as an immutable reference because the handler is a pure
66 /// JSON-to-DTO parser with no internal state.
67 #[must_use]
68 pub fn response_handler(&self) -> &ResponseHandler {
69 &self.response_handler
70 }
71
72 /// Borrow the notification handler.
73 ///
74 /// Returned as an immutable reference because the handler is a pure
75 /// JSON-to-DTO parser with no internal state.
76 #[must_use]
77 pub fn notification_handler(&self) -> &NotificationHandler {
78 &self.notification_handler
79 }
80
81 /// Classify and parse a raw JSON-RPC frame.
82 ///
83 /// Attempts the response path first (JSON-RPC 2.0 responses always
84 /// carry an `id`), then the notification path (notifications never
85 /// have `id`). Outbound-only [`JsonRpcRequest`]s — the third
86 /// [`MessageType`] variant — are produced by [`RequestBuilder`],
87 /// not by this parser, so receiving a "request-shaped" frame from
88 /// the server is treated as invalid data.
89 ///
90 /// # Errors
91 ///
92 /// Returns a [`serde_json::Error`] of kind [`std::io::ErrorKind::InvalidData`]
93 /// when `data` parses as neither a response nor a notification —
94 /// typically because it is malformed JSON, lacks both an `id` and a
95 /// `method`, or looks like an outbound request rather than an
96 /// inbound frame.
97 pub fn parse_message(&self, data: &str) -> Result<MessageType, serde_json::Error> {
98 // Try to parse as response first (has 'id' field)
99 if let Ok(response) = self.response_handler.parse_response(data) {
100 return Ok(MessageType::Response(response));
101 }
102
103 // Try to parse as notification (no 'id' field)
104 if let Ok(notification) = self.notification_handler.parse_notification(data) {
105 return Ok(MessageType::Notification(notification));
106 }
107
108 // If neither works, return error
109 Err(serde_json::Error::io(std::io::Error::new(
110 std::io::ErrorKind::InvalidData,
111 "Unable to parse message",
112 )))
113 }
114}
115
116/// Classification of a JSON-RPC 2.0 frame.
117///
118/// Emitted by [`MessageBuilder::parse_message`] to let callers dispatch
119/// on the three fundamental flavours of traffic in a JSON-RPC session.
120#[derive(Debug, Clone)]
121pub enum MessageType {
122 /// Outbound request frame — `method`, `params`, and a numeric `id`.
123 ///
124 /// Never produced by [`MessageBuilder::parse_message`]; reserved
125 /// for code paths that construct requests via [`RequestBuilder`]
126 /// and want to round-trip them through the same enum.
127 Request(JsonRpcRequest),
128 /// Inbound reply frame correlated with a previously-sent request
129 /// via a shared numeric `id`. Carries either a `result` or an
130 /// `error` payload (never both).
131 Response(JsonRpcResponse),
132 /// Server-initiated push frame. Has a `method` and `params` like a
133 /// request but no `id`, so it expects no reply. Examples:
134 /// subscription updates, heartbeats.
135 Notification(JsonRpcNotification),
136}