Skip to main content

nautilus_protocol/
wire.rs

1//! JSON-RPC 2.0 wire types and helpers.
2//!
3//! This module defines the base JSON-RPC 2.0 protocol structures.
4
5use serde::{Deserialize, Serialize};
6use serde_json::value::RawValue;
7use serde_json::Value;
8
9/// JSON-RPC 2.0 request identifier.
10///
11/// Can be a number, string, or null. The spec allows clients to omit the id
12/// for notifications (requests that don't expect a response).
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14#[serde(untagged)]
15pub enum RpcId {
16    Number(i64),
17    String(String),
18    Null,
19}
20
21/// JSON-RPC 2.0 request.
22///
23/// # Example
24///
25/// ```json
26/// {
27///   "jsonrpc": "2.0",
28///   "id": 1,
29///   "method": "engine.handshake",
30///   "params": { "protocolVersion": 1 }
31/// }
32/// ```
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct RpcRequest {
35    pub jsonrpc: String,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub id: Option<RpcId>,
38    pub method: String,
39    pub params: Value,
40}
41
42/// JSON-RPC 2.0 response.
43///
44/// Either contains a `result` (success) or an `error` (failure), but never both.
45///
46/// # Example Success
47///
48/// ```json
49/// {
50///   "jsonrpc": "2.0",
51///   "id": 1,
52///   "result": { "engineVersion": "0.1.0" }
53/// }
54/// ```
55///
56/// # Example Error
57///
58/// ```json
59/// {
60///   "jsonrpc": "2.0",
61///   "id": 1,
62///   "error": {
63///     "code": -32601,
64///     "message": "Method not found"
65///   }
66/// }
67/// ```
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct RpcResponse {
70    pub jsonrpc: String,
71    pub id: Option<RpcId>,
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub result: Option<Box<RawValue>>,
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub error: Option<RpcError>,
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub partial: Option<bool>,
78}
79
80/// JSON-RPC 2.0 error object.
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct RpcError {
83    pub code: i32,
84    pub message: String,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub data: Option<Value>,
87}
88
89// Standard JSON-RPC 2.0 error codes
90pub const PARSE_ERROR: i32 = -32700;
91pub const INVALID_REQUEST: i32 = -32600;
92pub const METHOD_NOT_FOUND: i32 = -32601;
93pub const INVALID_PARAMS: i32 = -32602;
94pub const INTERNAL_ERROR: i32 = -32603;
95
96/// Create a successful JSON-RPC response.
97pub fn ok(id: Option<RpcId>, result: Box<RawValue>) -> RpcResponse {
98    RpcResponse {
99        jsonrpc: "2.0".to_string(),
100        id,
101        result: Some(result),
102        error: None,
103        partial: None,
104    }
105}
106
107/// Create an error JSON-RPC response.
108pub fn err(id: Option<RpcId>, code: i32, message: String, data: Option<Value>) -> RpcResponse {
109    RpcResponse {
110        jsonrpc: "2.0".to_string(),
111        id,
112        result: None,
113        error: Some(RpcError {
114            code,
115            message,
116            data,
117        }),
118        partial: None,
119    }
120}
121
122/// Create a partial (chunked) JSON-RPC response. Used for streaming large result sets.
123pub fn ok_partial(id: Option<RpcId>, result: Box<RawValue>) -> RpcResponse {
124    RpcResponse {
125        jsonrpc: "2.0".to_string(),
126        id,
127        result: Some(result),
128        error: None,
129        partial: Some(true),
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use serde_json::json;
137
138    #[test]
139    fn test_rpc_id_serialization() {
140        // Number
141        let id = RpcId::Number(42);
142        let json = serde_json::to_string(&id).unwrap();
143        assert_eq!(json, "42");
144        let parsed: RpcId = serde_json::from_str(&json).unwrap();
145        assert_eq!(parsed, id);
146
147        // String
148        let id = RpcId::String("abc-123".to_string());
149        let json = serde_json::to_string(&id).unwrap();
150        assert_eq!(json, r#""abc-123""#);
151        let parsed: RpcId = serde_json::from_str(&json).unwrap();
152        assert_eq!(parsed, id);
153
154        // Null
155        let id = RpcId::Null;
156        let json = serde_json::to_string(&id).unwrap();
157        assert_eq!(json, "null");
158        let parsed: RpcId = serde_json::from_str(&json).unwrap();
159        assert_eq!(parsed, id);
160    }
161
162    #[test]
163    fn test_request_serialization() {
164        let request = RpcRequest {
165            jsonrpc: "2.0".to_string(),
166            id: Some(RpcId::Number(1)),
167            method: "test.method".to_string(),
168            params: json!({"key": "value"}),
169        };
170
171        let json = serde_json::to_value(&request).unwrap();
172        assert_eq!(json["jsonrpc"], "2.0");
173        assert_eq!(json["id"], 1);
174        assert_eq!(json["method"], "test.method");
175        assert_eq!(json["params"]["key"], "value");
176
177        // Round-trip
178        let parsed: RpcRequest = serde_json::from_value(json).unwrap();
179        assert_eq!(parsed.jsonrpc, "2.0");
180        assert_eq!(parsed.id, Some(RpcId::Number(1)));
181        assert_eq!(parsed.method, "test.method");
182    }
183
184    #[test]
185    fn test_response_ok() {
186        let raw = serde_json::value::to_raw_value(&json!({"status": "ok"})).unwrap();
187        let response = ok(Some(RpcId::Number(1)), raw);
188
189        assert_eq!(response.jsonrpc, "2.0");
190        assert_eq!(response.id, Some(RpcId::Number(1)));
191        assert!(response.result.is_some());
192        assert!(response.error.is_none());
193
194        let serialized = serde_json::to_string(&response).unwrap();
195        let json: serde_json::Value = serde_json::from_str(&serialized).unwrap();
196        assert_eq!(json["result"]["status"], "ok");
197        assert!(json.get("error").is_none());
198    }
199
200    #[test]
201    fn test_response_error() {
202        let response = err(
203            Some(RpcId::Number(2)),
204            METHOD_NOT_FOUND,
205            "Method not found".to_string(),
206            Some(json!({"method": "unknown.method"})),
207        );
208
209        assert_eq!(response.jsonrpc, "2.0");
210        assert_eq!(response.id, Some(RpcId::Number(2)));
211        assert!(response.result.is_none());
212        assert!(response.error.is_some());
213
214        let error = response.error.unwrap();
215        assert_eq!(error.code, METHOD_NOT_FOUND);
216        assert_eq!(error.message, "Method not found");
217        assert_eq!(error.data.unwrap()["method"], "unknown.method");
218    }
219
220    #[test]
221    fn test_notification_no_id() {
222        let request = RpcRequest {
223            jsonrpc: "2.0".to_string(),
224            id: None,
225            method: "notification".to_string(),
226            params: json!(null),
227        };
228
229        let json = serde_json::to_value(&request).unwrap();
230        assert!(json["id"].is_null() || !json.as_object().unwrap().contains_key("id"));
231    }
232}