aion_integrations/jsonrpc/
envelope.rs1use serde::{Deserialize, Serialize};
8
9pub const JSONRPC_VERSION: &str = "2.0";
11
12pub mod error_codes {
17 pub const PARSE_ERROR: i64 = -32700;
19 pub const INVALID_REQUEST: i64 = -32600;
21 pub const METHOD_NOT_FOUND: i64 = -32601;
23 pub const INVALID_PARAMS: i64 = -32602;
25 pub const INTERNAL_ERROR: i64 = -32603;
27}
28
29#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
34#[serde(untagged)]
35pub enum JsonRpcId {
36 Number(u64),
38 Text(String),
40}
41
42impl JsonRpcId {
43 #[must_use]
45 pub const fn number(value: u64) -> Self {
46 Self::Number(value)
47 }
48}
49
50#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
52pub struct JsonRpcRequest {
53 pub jsonrpc: String,
55 pub id: JsonRpcId,
57 pub method: String,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub params: Option<serde_json::Value>,
62}
63
64impl JsonRpcRequest {
65 #[must_use]
67 pub fn new(
68 id: JsonRpcId,
69 method: impl Into<String>,
70 params: Option<serde_json::Value>,
71 ) -> Self {
72 Self {
73 jsonrpc: JSONRPC_VERSION.to_owned(),
74 id,
75 method: method.into(),
76 params,
77 }
78 }
79}
80
81#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
86pub struct JsonRpcNotification {
87 pub jsonrpc: String,
89 pub method: String,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
93 pub params: Option<serde_json::Value>,
94}
95
96impl JsonRpcNotification {
97 #[must_use]
99 pub fn new(method: impl Into<String>, params: Option<serde_json::Value>) -> Self {
100 Self {
101 jsonrpc: JSONRPC_VERSION.to_owned(),
102 method: method.into(),
103 params,
104 }
105 }
106}
107
108#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
110pub struct JsonRpcError {
111 pub code: i64,
113 pub message: String,
115 #[serde(default, skip_serializing_if = "Option::is_none")]
117 pub data: Option<serde_json::Value>,
118}
119
120impl JsonRpcError {
121 #[must_use]
123 pub fn new(code: i64, message: impl Into<String>) -> Self {
124 Self {
125 code,
126 message: message.into(),
127 data: None,
128 }
129 }
130}
131
132#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
136pub struct JsonRpcResponse {
137 pub jsonrpc: String,
139 pub id: JsonRpcId,
141 #[serde(
148 default,
149 deserialize_with = "deserialize_present_value",
150 skip_serializing_if = "Option::is_none"
151 )]
152 pub result: Option<serde_json::Value>,
153 #[serde(default, skip_serializing_if = "Option::is_none")]
155 pub error: Option<JsonRpcError>,
156}
157
158fn deserialize_present_value<'de, D>(deserializer: D) -> Result<Option<serde_json::Value>, D::Error>
163where
164 D: serde::Deserializer<'de>,
165{
166 serde_json::Value::deserialize(deserializer).map(Some)
167}
168
169impl JsonRpcResponse {
170 #[must_use]
172 pub fn success(id: JsonRpcId, result: serde_json::Value) -> Self {
173 Self {
174 jsonrpc: JSONRPC_VERSION.to_owned(),
175 id,
176 result: Some(result),
177 error: None,
178 }
179 }
180
181 #[must_use]
183 pub fn failure(id: JsonRpcId, error: JsonRpcError) -> Self {
184 Self {
185 jsonrpc: JSONRPC_VERSION.to_owned(),
186 id,
187 result: None,
188 error: Some(error),
189 }
190 }
191}
192
193#[derive(Clone, Debug, PartialEq, Eq)]
200pub enum IncomingMessage {
201 Request(JsonRpcRequest),
203 Notification(JsonRpcNotification),
205 Response(JsonRpcResponse),
207}
208
209impl IncomingMessage {
210 pub fn from_value(value: serde_json::Value) -> Result<Self, serde_json::Error> {
216 let has_method = value.get("method").is_some();
217 let has_id = value.get("id").is_some_and(|id| !id.is_null());
218 if has_method {
219 if has_id {
220 serde_json::from_value(value).map(Self::Request)
221 } else {
222 serde_json::from_value(value).map(Self::Notification)
223 }
224 } else {
225 serde_json::from_value(value).map(Self::Response)
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use serde_json::json;
233
234 use super::{
235 IncomingMessage, JsonRpcError, JsonRpcId, JsonRpcNotification, JsonRpcRequest,
236 JsonRpcResponse, error_codes,
237 };
238
239 #[test]
240 fn request_serialises_with_id_and_omits_absent_params() -> Result<(), serde_json::Error> {
241 let request = JsonRpcRequest::new(JsonRpcId::number(1), "run/execute", None);
242 let value = serde_json::to_value(&request)?;
243 assert_eq!(value["jsonrpc"], "2.0");
244 assert_eq!(value["id"], 1);
245 assert_eq!(value["method"], "run/execute");
246 assert!(value.get("params").is_none(), "absent params are omitted");
247 Ok(())
248 }
249
250 #[test]
251 fn notification_never_carries_an_id() -> Result<(), serde_json::Error> {
252 let notification = JsonRpcNotification::new("event/message", Some(json!({ "text": "hi" })));
253 let value = serde_json::to_value(¬ification)?;
254 assert!(value.get("id").is_none(), "notifications carry no id");
255 assert_eq!(value["params"]["text"], "hi");
256 Ok(())
257 }
258
259 #[test]
260 fn classifies_request_notification_and_response() -> Result<(), serde_json::Error> {
261 let request = IncomingMessage::from_value(json!({
262 "jsonrpc": "2.0", "id": 7, "method": "intervene/inject", "params": {}
263 }))?;
264 assert!(matches!(request, IncomingMessage::Request(_)));
265
266 let notification = IncomingMessage::from_value(json!({
267 "jsonrpc": "2.0", "method": "event/stop", "params": {}
268 }))?;
269 assert!(matches!(notification, IncomingMessage::Notification(_)));
270
271 let response = IncomingMessage::from_value(json!({
272 "jsonrpc": "2.0", "id": 7, "result": { "ok": true }
273 }))?;
274 assert!(matches!(response, IncomingMessage::Response(_)));
275 Ok(())
276 }
277
278 #[test]
279 fn a_null_id_is_treated_as_a_notification_not_a_request() -> Result<(), serde_json::Error> {
280 let message = IncomingMessage::from_value(json!({
281 "jsonrpc": "2.0", "id": null, "method": "event/raw", "params": {}
282 }))?;
283 assert!(
284 matches!(message, IncomingMessage::Notification(_)),
285 "a null id must not make a method-bearing frame a correlated request"
286 );
287 Ok(())
288 }
289
290 #[test]
291 fn error_response_carries_result_none() {
292 let response = JsonRpcResponse::failure(
293 JsonRpcId::number(3),
294 JsonRpcError::new(error_codes::METHOD_NOT_FOUND, "method not found"),
295 );
296 assert!(response.result.is_none());
297 assert!(
298 matches!(response.error, Some(error) if error.code == error_codes::METHOD_NOT_FOUND),
299 "an error response carries the code"
300 );
301 }
302
303 #[test]
304 fn a_present_null_result_decodes_as_some_null() -> Result<(), serde_json::Error> {
305 let response: JsonRpcResponse = serde_json::from_value(json!({
308 "jsonrpc": "2.0", "id": 1, "result": null
309 }))?;
310 assert_eq!(response.result, Some(serde_json::Value::Null));
311 let wire = serde_json::to_value(&response)?;
312 assert!(
313 wire.as_object()
314 .is_some_and(|frame| frame.contains_key("result")),
315 "a present null result round-trips with the key on the wire"
316 );
317 Ok(())
318 }
319
320 #[test]
321 fn an_absent_result_decodes_as_none() -> Result<(), serde_json::Error> {
322 let response: JsonRpcResponse = serde_json::from_value(json!({
325 "jsonrpc": "2.0", "id": 1
326 }))?;
327 assert!(response.result.is_none());
328 assert!(response.error.is_none());
329 let wire = serde_json::to_value(&response)?;
330 assert!(
331 wire.as_object()
332 .is_some_and(|frame| !frame.contains_key("result")),
333 "an absent result stays absent on the wire"
334 );
335 Ok(())
336 }
337
338 #[test]
339 fn string_ids_round_trip() -> Result<(), serde_json::Error> {
340 let id = JsonRpcId::Text("abc".to_owned());
341 let value = serde_json::to_value(&id)?;
342 let decoded: JsonRpcId = serde_json::from_value(value)?;
343 assert_eq!(decoded, id);
344 Ok(())
345 }
346}