use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use super::error::RpcError;
pub const JSONRPC_VERSION: &str = "2.0";
#[derive(Serialize, Deserialize, schemars::JsonSchema, Debug, Clone)]
pub struct RpcRequest {
pub jsonrpc: String,
pub id: String,
pub method: String,
#[serde(default, skip_serializing_if = "is_null")]
pub params: serde_json::Value,
}
#[derive(Serialize, Deserialize, schemars::JsonSchema, Debug, Clone)]
pub struct RpcNotification {
pub jsonrpc: String,
pub method: String,
#[serde(default, skip_serializing_if = "is_null")]
pub params: serde_json::Value,
}
#[derive(Serialize, Deserialize, schemars::JsonSchema, Debug, Clone)]
pub struct RpcResponse {
pub jsonrpc: String,
pub id: Option<String>,
#[serde(flatten)]
pub payload: RpcResponsePayload,
}
#[derive(Serialize, Deserialize, schemars::JsonSchema, Debug, Clone)]
#[serde(untagged)]
pub enum RpcResponsePayload {
Ok { result: serde_json::Value },
Err { error: RpcError },
}
#[derive(Serialize, Deserialize, schemars::JsonSchema, Debug, Clone)]
#[serde(untagged)]
pub enum RpcMessage {
Response(RpcResponse),
Request(RpcRequest),
Notification(RpcNotification),
}
impl RpcRequest {
pub fn new<P: Serialize>(
id: impl Into<String>,
method: impl Into<String>,
params: &P,
) -> Result<Self, serde_json::Error> {
Ok(Self {
jsonrpc: JSONRPC_VERSION.to_string(),
id: id.into(),
method: method.into(),
params: serde_json::to_value(params)?,
})
}
}
impl RpcNotification {
pub fn new<P: Serialize>(
method: impl Into<String>,
params: &P,
) -> Result<Self, serde_json::Error> {
Ok(Self {
jsonrpc: JSONRPC_VERSION.to_string(),
method: method.into(),
params: serde_json::to_value(params)?,
})
}
}
impl RpcResponse {
pub fn ok<R: Serialize>(id: impl Into<String>, result: &R) -> Result<Self, serde_json::Error> {
Ok(Self {
jsonrpc: JSONRPC_VERSION.to_string(),
id: Some(id.into()),
payload: RpcResponsePayload::Ok {
result: serde_json::to_value(result)?,
},
})
}
pub fn err(id: impl Into<String>, error: RpcError) -> Self {
Self {
jsonrpc: JSONRPC_VERSION.to_string(),
id: Some(id.into()),
payload: RpcResponsePayload::Err { error },
}
}
pub fn err_anonymous(error: RpcError) -> Self {
Self {
jsonrpc: JSONRPC_VERSION.to_string(),
id: None,
payload: RpcResponsePayload::Err { error },
}
}
}
pub fn decode_params<P: DeserializeOwned + Default>(
value: serde_json::Value,
) -> Result<P, serde_json::Error> {
if value.is_null() {
Ok(P::default())
} else {
serde_json::from_value(value)
}
}
fn is_null(v: &serde_json::Value) -> bool {
v.is_null()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ipc::error::ErrorKind;
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct DummyParams {
foo: String,
bar: u32,
}
#[test]
fn request_round_trips_through_json() {
let req = RpcRequest::new(
"u1",
"system.handshake",
&DummyParams {
foo: "hello".into(),
bar: 7,
},
)
.expect("encode");
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("\"jsonrpc\":\"2.0\""), "wire: {json}");
assert!(json.contains("\"method\":\"system.handshake\""));
assert!(json.contains("\"id\":\"u1\""));
let back: RpcRequest = serde_json::from_str(&json).unwrap();
assert_eq!(back.id, "u1");
assert_eq!(back.method, "system.handshake");
let p: DummyParams = serde_json::from_value(back.params).unwrap();
assert_eq!(p.foo, "hello");
assert_eq!(p.bar, 7);
}
#[test]
fn request_without_params_omits_field_on_wire() {
let req = RpcRequest {
jsonrpc: JSONRPC_VERSION.into(),
id: "ping-1".into(),
method: "system.ping".into(),
params: serde_json::Value::Null,
};
let v = serde_json::to_value(&req).unwrap();
assert!(v.get("params").is_none(), "wire: {v:?}");
}
#[test]
fn notification_decodes_without_id() {
let wire = r#"{"jsonrpc":"2.0","method":"notifications.new",
"params":{"id":"notif-9f3a"}}"#;
let m: RpcMessage = serde_json::from_str(wire).unwrap();
match m {
RpcMessage::Notification(n) => {
assert_eq!(n.method, "notifications.new");
assert_eq!(n.params["id"], "notif-9f3a");
}
other => panic!("expected Notification, got {other:?}"),
}
}
#[test]
fn success_response_decodes_and_round_trips() {
let r =
RpcResponse::ok("u3", &serde_json::json!({"subscription":"sub-n-1"})).expect("encode");
let json = serde_json::to_string(&r).unwrap();
let v: serde_json::Value = serde_json::from_str(&json).unwrap();
assert!(v.get("result").is_some(), "wire: {v:?}");
assert!(v.get("error").is_none());
let m: RpcMessage = serde_json::from_str(&json).unwrap();
assert!(matches!(m, RpcMessage::Response(_)));
}
#[test]
fn error_response_decodes_and_round_trips() {
let r = RpcResponse::err(
"u5",
RpcError::new(
ErrorKind::Unauthorized,
"manifest 'reboot' has user_invokable=false",
),
);
let json = serde_json::to_string(&r).unwrap();
let v: serde_json::Value = serde_json::from_str(&json).unwrap();
assert!(v.get("error").is_some(), "wire: {v:?}");
assert!(v.get("result").is_none());
assert_eq!(v["error"]["code"], -32000);
let back: RpcResponse = serde_json::from_str(&json).unwrap();
match back.payload {
RpcResponsePayload::Err { error } => assert_eq!(error.code, -32000),
other => panic!("expected Err payload, got {other:?}"),
}
}
#[test]
fn message_decoder_distinguishes_request_from_response() {
let request_wire = r#"{"jsonrpc":"2.0","id":"u1","method":"system.ping"}"#;
let response_wire = r#"{"jsonrpc":"2.0","id":"u1","result":null}"#;
match serde_json::from_str::<RpcMessage>(request_wire).unwrap() {
RpcMessage::Request(r) => assert_eq!(r.method, "system.ping"),
other => panic!("expected Request, got {other:?}"),
}
match serde_json::from_str::<RpcMessage>(response_wire).unwrap() {
RpcMessage::Response(r) => assert_eq!(r.id.as_deref(), Some("u1")),
other => panic!("expected Response, got {other:?}"),
}
}
#[test]
fn void_result_serialises_as_null() {
let r = RpcResponse::ok("u4", &()).expect("encode");
let v = serde_json::to_value(&r).unwrap();
assert!(v["result"].is_null(), "wire: {v}");
}
#[test]
fn err_anonymous_serialises_id_as_null() {
let r = RpcResponse::err_anonymous(RpcError::bare(ErrorKind::ParseError));
let v = serde_json::to_value(&r).unwrap();
assert!(v["id"].is_null(), "wire: {v}");
assert_eq!(v["error"]["code"], -32700);
}
#[test]
fn anonymous_error_response_round_trips() {
let wire = r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"Parse error"}}"#;
let back: RpcResponse = serde_json::from_str(wire).expect("decode");
assert!(back.id.is_none(), "decoded id should be None for null wire");
match back.payload {
RpcResponsePayload::Err { error } => assert_eq!(error.code, -32700),
other => panic!("expected Err payload, got {other:?}"),
}
}
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
struct EmptyParams {}
#[derive(Serialize, Deserialize, Default, Debug, PartialEq)]
struct WithDefaults {
#[serde(default)]
lines: u32,
}
#[test]
fn decode_params_treats_null_as_default() {
let p: EmptyParams = decode_params(serde_json::Value::Null).expect("null → default");
assert_eq!(p, EmptyParams {});
let p: WithDefaults = decode_params(serde_json::Value::Null).expect("null → default");
assert_eq!(p.lines, 0);
}
#[test]
fn decode_params_passes_through_explicit_object() {
let p: WithDefaults =
decode_params(serde_json::json!({"lines": 42})).expect("explicit object");
assert_eq!(p.lines, 42);
let err: Result<WithDefaults, _> = decode_params(serde_json::json!(["wrong", "shape"]));
assert!(err.is_err(), "non-object input must still fail");
}
}