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
89pub const PARSE_ERROR: i32 = -32700;
90pub const INVALID_REQUEST: i32 = -32600;
91pub const METHOD_NOT_FOUND: i32 = -32601;
92pub const INVALID_PARAMS: i32 = -32602;
93pub const INTERNAL_ERROR: i32 = -32603;
94
95/// Create a successful JSON-RPC response.
96pub fn ok(id: Option<RpcId>, result: Box<RawValue>) -> RpcResponse {
97    RpcResponse {
98        jsonrpc: "2.0".to_string(),
99        id,
100        result: Some(result),
101        error: None,
102        partial: None,
103    }
104}
105
106/// Create an error JSON-RPC response.
107pub fn err(id: Option<RpcId>, code: i32, message: String, data: Option<Value>) -> RpcResponse {
108    RpcResponse {
109        jsonrpc: "2.0".to_string(),
110        id,
111        result: None,
112        error: Some(RpcError {
113            code,
114            message,
115            data,
116        }),
117        partial: None,
118    }
119}
120
121/// Create a partial (chunked) JSON-RPC response. Used for streaming large result sets.
122pub fn ok_partial(id: Option<RpcId>, result: Box<RawValue>) -> RpcResponse {
123    RpcResponse {
124        jsonrpc: "2.0".to_string(),
125        id,
126        result: Some(result),
127        error: None,
128        partial: Some(true),
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use serde_json::json;
136
137    #[test]
138    fn test_rpc_id_serialization() {
139        let id = RpcId::Number(42);
140        let json = serde_json::to_string(&id).unwrap();
141        assert_eq!(json, "42");
142        let parsed: RpcId = serde_json::from_str(&json).unwrap();
143        assert_eq!(parsed, id);
144
145        let id = RpcId::String("abc-123".to_string());
146        let json = serde_json::to_string(&id).unwrap();
147        assert_eq!(json, r#""abc-123""#);
148        let parsed: RpcId = serde_json::from_str(&json).unwrap();
149        assert_eq!(parsed, id);
150
151        let id = RpcId::Null;
152        let json = serde_json::to_string(&id).unwrap();
153        assert_eq!(json, "null");
154        let parsed: RpcId = serde_json::from_str(&json).unwrap();
155        assert_eq!(parsed, id);
156    }
157
158    #[test]
159    fn test_request_serialization() {
160        let request = RpcRequest {
161            jsonrpc: "2.0".to_string(),
162            id: Some(RpcId::Number(1)),
163            method: "test.method".to_string(),
164            params: json!({"key": "value"}),
165        };
166
167        let json = serde_json::to_value(&request).unwrap();
168        assert_eq!(json["jsonrpc"], "2.0");
169        assert_eq!(json["id"], 1);
170        assert_eq!(json["method"], "test.method");
171        assert_eq!(json["params"]["key"], "value");
172
173        let parsed: RpcRequest = serde_json::from_value(json).unwrap();
174        assert_eq!(parsed.jsonrpc, "2.0");
175        assert_eq!(parsed.id, Some(RpcId::Number(1)));
176        assert_eq!(parsed.method, "test.method");
177    }
178
179    #[test]
180    fn test_response_ok() {
181        let raw = serde_json::value::to_raw_value(&json!({"status": "ok"})).unwrap();
182        let response = ok(Some(RpcId::Number(1)), raw);
183
184        assert_eq!(response.jsonrpc, "2.0");
185        assert_eq!(response.id, Some(RpcId::Number(1)));
186        assert!(response.result.is_some());
187        assert!(response.error.is_none());
188
189        let serialized = serde_json::to_string(&response).unwrap();
190        let json: serde_json::Value = serde_json::from_str(&serialized).unwrap();
191        assert_eq!(json["result"]["status"], "ok");
192        assert!(json.get("error").is_none());
193    }
194
195    #[test]
196    fn test_response_error() {
197        let response = err(
198            Some(RpcId::Number(2)),
199            METHOD_NOT_FOUND,
200            "Method not found".to_string(),
201            Some(json!({"method": "unknown.method"})),
202        );
203
204        assert_eq!(response.jsonrpc, "2.0");
205        assert_eq!(response.id, Some(RpcId::Number(2)));
206        assert!(response.result.is_none());
207        assert!(response.error.is_some());
208
209        let error = response.error.unwrap();
210        assert_eq!(error.code, METHOD_NOT_FOUND);
211        assert_eq!(error.message, "Method not found");
212        assert_eq!(error.data.unwrap()["method"], "unknown.method");
213    }
214
215    #[test]
216    fn test_notification_no_id() {
217        let request = RpcRequest {
218            jsonrpc: "2.0".to_string(),
219            id: None,
220            method: "notification".to_string(),
221            params: json!(null),
222        };
223
224        let json = serde_json::to_value(&request).unwrap();
225        assert!(json["id"].is_null() || !json.as_object().unwrap().contains_key("id"));
226    }
227}