feat(ipc): JSON-RPC envelope types

This commit is contained in:
2026-05-25 11:45:37 +02:00
parent a3c979511e
commit 53f6b82f2b
2 changed files with 121 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Request {
pub jsonrpc: String,
pub id: serde_json::Value,
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Notification {
pub jsonrpc: String,
pub method: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Response {
pub jsonrpc: String,
pub id: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<RpcError>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcError {
pub code: i32,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Incoming {
Request(Request),
Response(Response),
Notification(Notification),
}
pub const JSONRPC_VERSION: &str = "2.0";
pub fn request(id: u64, method: &str, params: Option<Value>) -> Request {
Request {
jsonrpc: JSONRPC_VERSION.into(),
id: serde_json::json!(id),
method: method.into(),
params,
}
}
pub fn notification(method: &str, params: Option<Value>) -> Notification {
Notification {
jsonrpc: JSONRPC_VERSION.into(),
method: method.into(),
params,
}
}
pub fn ok_response(id: serde_json::Value, result: Value) -> Response {
Response {
jsonrpc: JSONRPC_VERSION.into(),
id,
result: Some(result),
error: None,
}
}
pub fn err_response(id: serde_json::Value, code: i32, message: String) -> Response {
Response {
jsonrpc: JSONRPC_VERSION.into(),
id,
result: None,
error: Some(RpcError {
code,
message,
data: None,
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_round_trip() {
let r = request(7, "list", None);
let s = serde_json::to_string(&r).unwrap();
let back: Request = serde_json::from_str(&s).unwrap();
assert_eq!(back.method, "list");
assert_eq!(back.id, serde_json::json!(7));
}
#[test]
fn incoming_is_response() {
let s = r#"{"jsonrpc":"2.0","id":1,"result":{"ok":true}}"#;
let i: Incoming = serde_json::from_str(s).unwrap();
assert!(matches!(i, Incoming::Response(_)));
}
#[test]
fn incoming_is_notification() {
let s = r#"{"jsonrpc":"2.0","method":"log","params":{}}"#;
let i: Incoming = serde_json::from_str(s).unwrap();
assert!(matches!(i, Incoming::Notification(_)));
}
}
+7
View File
@@ -1 +1,8 @@
//! JSON-RPC 2.0 over newline-delimited JSON on a Unix socket.
pub mod envelope;
pub use envelope::{
err_response, notification, ok_response, request, Incoming, Notification, Request, Response,
RpcError,
};