1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9pub const JSONRPC_VERSION: &str = "2.0";
11
12fn jsonrpc_version() -> String {
13 JSONRPC_VERSION.to_string()
14}
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
18pub struct Request {
19 #[serde(default = "jsonrpc_version")]
20 pub jsonrpc: String,
21 pub id: u64,
22 pub method: String,
23 #[serde(default)]
26 pub params: Value,
27}
28
29impl Request {
30 pub fn new(id: u64, method: impl Into<String>, params: Value) -> Self {
31 Self {
32 jsonrpc: jsonrpc_version(),
33 id,
34 method: method.into(),
35 params,
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub struct Notification {
43 #[serde(default = "jsonrpc_version")]
44 pub jsonrpc: String,
45 pub method: String,
46 #[serde(default)]
47 pub params: Value,
48}
49
50impl Notification {
51 pub fn new(method: impl Into<String>, params: Value) -> Self {
52 Self {
53 jsonrpc: jsonrpc_version(),
54 method: method.into(),
55 params,
56 }
57 }
58}
59
60#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct Response {
66 #[serde(default = "jsonrpc_version")]
67 pub jsonrpc: String,
68 pub id: u64,
69 #[serde(flatten)]
70 pub outcome: ResponseOutcome,
71}
72
73#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
74#[serde(untagged)]
75pub enum ResponseOutcome {
76 Success { result: Value },
77 Error { error: RpcError },
78}
79
80impl Response {
81 pub fn success(id: u64, result: Value) -> Self {
82 Self {
83 jsonrpc: jsonrpc_version(),
84 id,
85 outcome: ResponseOutcome::Success { result },
86 }
87 }
88
89 pub fn error(id: u64, error: RpcError) -> Self {
90 Self {
91 jsonrpc: jsonrpc_version(),
92 id,
93 outcome: ResponseOutcome::Error { error },
94 }
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
101pub struct RpcError {
102 pub code: i32,
103 pub message: String,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub data: Option<Value>,
106}
107
108impl RpcError {
109 pub const PARSE_ERROR: i32 = -32700;
111 pub const INVALID_REQUEST: i32 = -32600;
112 pub const METHOD_NOT_FOUND: i32 = -32601;
113 pub const INVALID_PARAMS: i32 = -32602;
114 pub const INTERNAL_ERROR: i32 = -32603;
115
116 pub const NOT_SUPPORTED: i32 = -32000;
121
122 pub const UNAUTHORIZED: i32 = -32001;
128
129 pub fn new(code: i32, message: impl Into<String>) -> Self {
130 Self {
131 code,
132 message: message.into(),
133 data: None,
134 }
135 }
136
137 pub fn not_supported(message: impl Into<String>) -> Self {
138 Self::new(Self::NOT_SUPPORTED, message)
139 }
140
141 pub fn unauthorized(message: impl Into<String>) -> Self {
142 Self::new(Self::UNAUTHORIZED, message)
143 }
144}
145
146#[derive(Debug, Clone, PartialEq)]
149pub enum Incoming {
150 Request(Request),
151 Response(Response),
152 Notification(Notification),
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
160 fn request_round_trips_through_json() {
161 let req = Request::new(7, "handshake", Value::Null);
162 let json = serde_json::to_string(&req).unwrap();
163 let back: Request = serde_json::from_str(&json).unwrap();
164 assert_eq!(req, back);
165 }
166
167 #[test]
168 fn request_missing_jsonrpc_field_still_decodes() {
169 let line = r#"{"id":1,"method":"handshake","params":null}"#;
170 let req: Request = serde_json::from_str(line).unwrap();
171 assert_eq!(req.jsonrpc, JSONRPC_VERSION);
172 }
173
174 #[test]
175 fn response_success_round_trips_and_omits_error_key() {
176 let resp = Response::success(3, serde_json::json!({"ok": true}));
177 let json = serde_json::to_string(&resp).unwrap();
178 assert!(json.contains("\"result\""));
179 assert!(!json.contains("\"error\""));
180 let back: Response = serde_json::from_str(&json).unwrap();
181 assert_eq!(resp, back);
182 }
183
184 #[test]
185 fn response_error_round_trips_and_omits_result_key() {
186 let resp = Response::error(3, RpcError::not_supported("screenshot disabled"));
187 let json = serde_json::to_string(&resp).unwrap();
188 assert!(json.contains("\"error\""));
189 assert!(!json.contains("\"result\""));
190 let back: Response = serde_json::from_str(&json).unwrap();
191 assert_eq!(resp, back);
192 match back.outcome {
193 ResponseOutcome::Error { error } => {
194 assert_eq!(error.code, RpcError::NOT_SUPPORTED);
195 }
196 ResponseOutcome::Success { .. } => panic!("expected an error outcome"),
197 }
198 }
199
200 #[test]
201 fn the_custom_error_codes_stay_inside_the_reserved_range() {
202 for code in [RpcError::NOT_SUPPORTED, RpcError::UNAUTHORIZED] {
206 assert!((-32099..=-32000).contains(&code), "out of range: {code}");
207 }
208 assert_ne!(RpcError::NOT_SUPPORTED, RpcError::UNAUTHORIZED);
209 assert_eq!(RpcError::unauthorized("nope").code, RpcError::UNAUTHORIZED);
210 }
211
212 #[test]
213 fn rpc_error_data_field_omitted_when_none() {
214 let err = RpcError::new(RpcError::INTERNAL_ERROR, "boom");
215 let json = serde_json::to_string(&err).unwrap();
216 assert!(!json.contains("\"data\""));
217 }
218
219 #[test]
220 fn unknown_fields_are_tolerated() {
221 let line =
225 r#"{"jsonrpc":"2.0","id":1,"method":"handshake","params":null,"extra":"future field"}"#;
226 let req: Request = serde_json::from_str(line).unwrap();
227 assert_eq!(req.method, "handshake");
228 }
229}