Skip to main content

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}