use serde::{Deserialize, Serialize};
use serde_json::Value;
pub const JSONRPC_VERSION: &str = "2.0";
fn jsonrpc_version() -> String {
JSONRPC_VERSION.to_string()
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Request {
#[serde(default = "jsonrpc_version")]
pub jsonrpc: String,
pub id: u64,
pub method: String,
#[serde(default)]
pub params: Value,
}
impl Request {
pub fn new(id: u64, method: impl Into<String>, params: Value) -> Self {
Self {
jsonrpc: jsonrpc_version(),
id,
method: method.into(),
params,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Notification {
#[serde(default = "jsonrpc_version")]
pub jsonrpc: String,
pub method: String,
#[serde(default)]
pub params: Value,
}
impl Notification {
pub fn new(method: impl Into<String>, params: Value) -> Self {
Self {
jsonrpc: jsonrpc_version(),
method: method.into(),
params,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Response {
#[serde(default = "jsonrpc_version")]
pub jsonrpc: String,
pub id: u64,
#[serde(flatten)]
pub outcome: ResponseOutcome,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ResponseOutcome {
Success { result: Value },
Error { error: RpcError },
}
impl Response {
pub fn success(id: u64, result: Value) -> Self {
Self {
jsonrpc: jsonrpc_version(),
id,
outcome: ResponseOutcome::Success { result },
}
}
pub fn error(id: u64, error: RpcError) -> Self {
Self {
jsonrpc: jsonrpc_version(),
id,
outcome: ResponseOutcome::Error { error },
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RpcError {
pub code: i32,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
impl RpcError {
pub const PARSE_ERROR: i32 = -32700;
pub const INVALID_REQUEST: i32 = -32600;
pub const METHOD_NOT_FOUND: i32 = -32601;
pub const INVALID_PARAMS: i32 = -32602;
pub const INTERNAL_ERROR: i32 = -32603;
pub const NOT_SUPPORTED: i32 = -32000;
pub const UNAUTHORIZED: i32 = -32001;
pub fn new(code: i32, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
data: None,
}
}
pub fn not_supported(message: impl Into<String>) -> Self {
Self::new(Self::NOT_SUPPORTED, message)
}
pub fn unauthorized(message: impl Into<String>) -> Self {
Self::new(Self::UNAUTHORIZED, message)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Incoming {
Request(Request),
Response(Response),
Notification(Notification),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_round_trips_through_json() {
let req = Request::new(7, "handshake", Value::Null);
let json = serde_json::to_string(&req).unwrap();
let back: Request = serde_json::from_str(&json).unwrap();
assert_eq!(req, back);
}
#[test]
fn request_missing_jsonrpc_field_still_decodes() {
let line = r#"{"id":1,"method":"handshake","params":null}"#;
let req: Request = serde_json::from_str(line).unwrap();
assert_eq!(req.jsonrpc, JSONRPC_VERSION);
}
#[test]
fn response_success_round_trips_and_omits_error_key() {
let resp = Response::success(3, serde_json::json!({"ok": true}));
let json = serde_json::to_string(&resp).unwrap();
assert!(json.contains("\"result\""));
assert!(!json.contains("\"error\""));
let back: Response = serde_json::from_str(&json).unwrap();
assert_eq!(resp, back);
}
#[test]
fn response_error_round_trips_and_omits_result_key() {
let resp = Response::error(3, RpcError::not_supported("screenshot disabled"));
let json = serde_json::to_string(&resp).unwrap();
assert!(json.contains("\"error\""));
assert!(!json.contains("\"result\""));
let back: Response = serde_json::from_str(&json).unwrap();
assert_eq!(resp, back);
match back.outcome {
ResponseOutcome::Error { error } => {
assert_eq!(error.code, RpcError::NOT_SUPPORTED);
}
ResponseOutcome::Success { .. } => panic!("expected an error outcome"),
}
}
#[test]
fn the_custom_error_codes_stay_inside_the_reserved_range() {
for code in [RpcError::NOT_SUPPORTED, RpcError::UNAUTHORIZED] {
assert!((-32099..=-32000).contains(&code), "out of range: {code}");
}
assert_ne!(RpcError::NOT_SUPPORTED, RpcError::UNAUTHORIZED);
assert_eq!(RpcError::unauthorized("nope").code, RpcError::UNAUTHORIZED);
}
#[test]
fn rpc_error_data_field_omitted_when_none() {
let err = RpcError::new(RpcError::INTERNAL_ERROR, "boom");
let json = serde_json::to_string(&err).unwrap();
assert!(!json.contains("\"data\""));
}
#[test]
fn unknown_fields_are_tolerated() {
let line =
r#"{"jsonrpc":"2.0","id":1,"method":"handshake","params":null,"extra":"future field"}"#;
let req: Request = serde_json::from_str(line).unwrap();
assert_eq!(req.method, "handshake");
}
}