jsonrpc_lite/jsonrpc.rs
1//! JSON-RPC 2.0 implementation
2//!
3//! This module provides types and utilities for working with JSON-RPC 2.0 protocol.
4//! It implements the core JSON-RPC 2.0 objects as defined in the specification,
5//! including requests, notifications, and responses.
6//!
7//! The main type is `JsonRpc` which represents all possible JSON-RPC message types.
8//! Helper methods are provided for creating and parsing JSON-RPC messages.
9
10use serde::{Deserialize, Serialize};
11use serde_json::{Map, Result as SerdeResult, Value};
12
13use crate::Error as RpcError;
14
15/// An identifier established by the Client that MUST contain a String, Number,
16/// or NULL value if included. If it is not included it is assumed to be a notification.
17/// The value SHOULD normally not be Null and Numbers SHOULD NOT contain fractional parts
18///
19/// As per the JSON-RPC 2.0 specification, this identifier is used to correlate
20/// requests with their corresponding responses.
21#[derive(Clone, Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]
22#[serde(untagged)]
23pub enum Id {
24 /// Numeric identifier
25 Num(i64),
26 /// String identifier
27 Str(String),
28 /// Null identifier (represented as unit type in Rust)
29 None(()),
30}
31
32impl From<()> for Id {
33 /// Converts unit type to Id::None
34 ///
35 /// # Arguments
36 ///
37 /// * `val` - The unit value to convert
38 ///
39 /// # Returns
40 ///
41 /// A new Id::None variant
42 fn from(val: ()) -> Self {
43 Id::None(val)
44 }
45}
46
47impl From<i64> for Id {
48 /// Converts an i64 to Id::Num
49 ///
50 /// # Arguments
51 ///
52 /// * `val` - The i64 value to convert
53 ///
54 /// # Returns
55 ///
56 /// A new Id::Num variant containing the provided value
57 fn from(val: i64) -> Self {
58 Id::Num(val)
59 }
60}
61
62impl From<String> for Id {
63 /// Converts a String to Id::Str
64 ///
65 /// # Arguments
66 ///
67 /// * `val` - The String value to convert
68 ///
69 /// # Returns
70 ///
71 /// A new Id::Str variant containing the provided value
72 fn from(val: String) -> Self {
73 Id::Str(val)
74 }
75}
76
77/// A Structured value that holds the parameter values
78/// to be used during the invocation of the method.
79/// This member MAY be omitted.
80///
81/// Parameters can be provided as either an ordered array or a named map,
82/// as per the JSON-RPC 2.0 specification.
83#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
84#[serde(untagged)]
85pub enum Params {
86 /// Parameters as an ordered array of values
87 Array(Vec<Value>),
88 /// Parameters as a map of named values
89 Map(Map<String, Value>),
90 /// No parameters (represented as unit type in Rust)
91 None(()),
92}
93
94impl From<Value> for Params {
95 /// Converts a serde_json::Value to Params
96 ///
97 /// # Arguments
98 ///
99 /// * `val` - The Value to convert
100 ///
101 /// # Returns
102 ///
103 /// - Params::Array if the value is an array
104 /// - Params::Map if the value is an object
105 /// - Params::None otherwise
106 fn from(val: Value) -> Self {
107 match val {
108 Value::Array(v) => Params::Array(v),
109 Value::Object(v) => Params::Map(v),
110 _ => Params::None(()),
111 }
112 }
113}
114
115impl From<Vec<Value>> for Params {
116 /// Converts a Vec<Value> to Params::Array
117 ///
118 /// # Arguments
119 ///
120 /// * `val` - The vector to convert
121 ///
122 /// # Returns
123 ///
124 /// A new Params::Array variant containing the provided vector
125 fn from(val: Vec<Value>) -> Self {
126 Params::Array(val)
127 }
128}
129
130impl From<Map<String, Value>> for Params {
131 /// Converts a Map<String, Value> to Params::Map
132 ///
133 /// # Arguments
134 ///
135 /// * `val` - The map to convert
136 ///
137 /// # Returns
138 ///
139 /// A new Params::Map variant containing the provided map
140 fn from(val: Map<String, Value>) -> Self {
141 Params::Map(val)
142 }
143}
144
145/// JSON-RPC 2.0 Request object
146///
147/// A request object represents a call to a method on the server.
148/// It contains a method name, optional parameters, and an identifier
149/// that will be used to match the response to this request.
150#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
151pub struct Request {
152 /// JSON-RPC protocol version (always "2.0")
153 jsonrpc: String,
154 /// Name of the method to be invoked
155 method: String,
156 /// Parameters to be used during the invocation of the method
157 #[serde(skip_serializing_if = "Option::is_none")]
158 params: Option<Params>,
159 /// Client-established identifier for this request
160 id: Id,
161}
162
163/// JSON-RPC 2.0 Notification object
164///
165/// A notification is similar to a request but does not require a response.
166/// It contains a method name and optional parameters, but no identifier.
167#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
168pub struct Notification {
169 /// JSON-RPC protocol version (always "2.0")
170 jsonrpc: String,
171 /// Name of the method to be invoked
172 method: String,
173 /// Parameters to be used during the invocation of the method
174 #[serde(skip_serializing_if = "Option::is_none")]
175 params: Option<Params>,
176}
177
178/// JSON-RPC 2.0 Success Response object
179///
180/// A success response is sent when a request has been processed successfully.
181/// It contains the result of the method call and the identifier from the request.
182#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
183pub struct Success {
184 /// JSON-RPC protocol version (always "2.0")
185 jsonrpc: String,
186 /// The result of the method call
187 result: Value,
188 /// Client-established identifier matching the request
189 id: Id,
190}
191
192/// JSON-RPC 2.0 Error Response object
193///
194/// An error response is sent when a request could not be processed successfully.
195/// It contains an error object and the identifier from the request.
196#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
197pub struct Error {
198 /// JSON-RPC protocol version (always "2.0")
199 jsonrpc: String,
200 /// The error that occurred
201 error: RpcError,
202 /// Client-established identifier matching the request
203 id: Id,
204}
205
206/// JSON-RPC 2.0 Request object and Response object
207/// [JSON-RPC 2.0 Specification](http://www.jsonrpc.org/specification).
208///
209/// This enum represents all possible JSON-RPC message types:
210/// - Request: A method call with an identifier
211/// - Notification: A method call without an identifier (no response expected)
212/// - Success: A successful response to a request
213/// - Error: An error response to a request
214#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
215#[serde(untagged)]
216pub enum JsonRpc {
217 /// Request object
218 Request(Request),
219 /// Notification object
220 Notification(Notification),
221 /// Success Response
222 Success(Success),
223 /// Error Response
224 Error(Error),
225}
226
227impl JsonRpc {
228 /// Creates a JSON-RPC 2.0 request object without params
229 ///
230 /// # Arguments
231 ///
232 /// * `id` - The identifier for the request
233 /// * `method` - The name of the method to call
234 ///
235 /// # Returns
236 ///
237 /// A new JsonRpc::Request variant
238 ///
239 /// # Examples
240 ///
241 /// ```
242 /// use jsonrpc_lite::JsonRpc;
243 ///
244 /// let request = JsonRpc::request(1, "echo");
245 /// ```
246 pub fn request<I: Into<Id>>(id: I, method: &str) -> Self {
247 JsonRpc::Request(Request {
248 jsonrpc: String::from("2.0"),
249 method: String::from(method),
250 params: None,
251 id: id.into(),
252 })
253 }
254
255 /// Creates a JSON-RPC 2.0 request object with params
256 ///
257 /// # Arguments
258 ///
259 /// * `id` - The identifier for the request
260 /// * `method` - The name of the method to call
261 /// * `params` - The parameters to pass to the method
262 ///
263 /// # Returns
264 ///
265 /// A new JsonRpc::Request variant with parameters
266 ///
267 /// # Examples
268 ///
269 /// ```
270 /// use jsonrpc_lite::JsonRpc;
271 /// use serde_json::json;
272 ///
273 /// let request = JsonRpc::request_with_params(1, "add", json!([1, 2]));
274 /// ```
275 pub fn request_with_params<I: Into<Id>, P: Into<Params>>(
276 id: I,
277 method: &str,
278 params: P,
279 ) -> Self {
280 JsonRpc::Request(Request {
281 jsonrpc: String::from("2.0"),
282 method: String::from(method),
283 params: Some(params.into()),
284 id: id.into(),
285 })
286 }
287
288 /// Creates a JSON-RPC 2.0 notification object without params
289 ///
290 /// # Arguments
291 ///
292 /// * `method` - The name of the method to call
293 ///
294 /// # Returns
295 ///
296 /// A new JsonRpc::Notification variant
297 ///
298 /// # Examples
299 ///
300 /// ```
301 /// use jsonrpc_lite::JsonRpc;
302 ///
303 /// let notification = JsonRpc::notification("ping");
304 /// ```
305 pub fn notification(method: &str) -> Self {
306 JsonRpc::Notification(Notification {
307 jsonrpc: String::from("2.0"),
308 method: String::from(method),
309 params: None,
310 })
311 }
312
313 /// Creates a JSON-RPC 2.0 notification object with params
314 ///
315 /// # Arguments
316 ///
317 /// * `method` - The name of the method to call
318 /// * `params` - The parameters to pass to the method
319 ///
320 /// # Returns
321 ///
322 /// A new JsonRpc::Notification variant with parameters
323 ///
324 /// # Examples
325 ///
326 /// ```
327 /// use jsonrpc_lite::JsonRpc;
328 /// use serde_json::json;
329 ///
330 /// let notification = JsonRpc::notification_with_params("log", json!({"level": "info", "message": "Hello"}));
331 /// ```
332 pub fn notification_with_params<P: Into<Params>>(method: &str, params: P) -> Self {
333 JsonRpc::Notification(Notification {
334 jsonrpc: String::from("2.0"),
335 method: String::from(method),
336 params: Some(params.into()),
337 })
338 }
339
340 /// Creates a JSON-RPC 2.0 success response object
341 ///
342 /// # Arguments
343 ///
344 /// * `id` - The identifier matching the request
345 /// * `result` - The result of the method call
346 ///
347 /// # Returns
348 ///
349 /// A new JsonRpc::Success variant
350 ///
351 /// # Examples
352 ///
353 /// ```
354 /// use jsonrpc_lite::JsonRpc;
355 /// use serde_json::json;
356 ///
357 /// let response = JsonRpc::success(1, &json!(42));
358 /// ```
359 pub fn success<I: Into<Id>>(id: I, result: &Value) -> Self {
360 JsonRpc::Success(Success {
361 jsonrpc: String::from("2.0"),
362 result: result.clone(),
363 id: id.into(),
364 })
365 }
366
367 /// Creates a JSON-RPC 2.0 error response object
368 ///
369 /// # Arguments
370 ///
371 /// * `id` - The identifier matching the request
372 /// * `error` - The error that occurred
373 ///
374 /// # Returns
375 ///
376 /// A new JsonRpc::Error variant
377 ///
378 /// # Examples
379 ///
380 /// ```
381 /// use jsonrpc_lite::{JsonRpc, Error};
382 ///
383 /// let response = JsonRpc::error(1, Error::method_not_found());
384 /// ```
385 pub fn error<I: Into<Id>>(id: I, error: RpcError) -> Self {
386 JsonRpc::Error(Error {
387 jsonrpc: String::from("2.0"),
388 error,
389 id: id.into(),
390 })
391 }
392
393 /// Gets the JSON-RPC protocol version
394 ///
395 /// # Returns
396 ///
397 /// The protocol version string ("2.0") or None if not available
398 pub fn get_version(&self) -> Option<&str> {
399 match self {
400 JsonRpc::Notification(ref v) => Some(&v.jsonrpc),
401 JsonRpc::Request(ref v) => Some(&v.jsonrpc),
402 JsonRpc::Success(ref v) => Some(&v.jsonrpc),
403 JsonRpc::Error(ref v) => Some(&v.jsonrpc),
404 }
405 }
406
407 /// Gets the identifier from the JSON-RPC message
408 ///
409 /// # Returns
410 ///
411 /// The identifier if present (for requests and responses), or None for notifications
412 pub fn get_id(&self) -> Option<Id> {
413 match *self {
414 JsonRpc::Request(ref v) => Some(v.id.clone()),
415 JsonRpc::Success(ref v) => Some(v.id.clone()),
416 JsonRpc::Error(ref v) => Some(v.id.clone()),
417 _ => None,
418 }
419 }
420
421 /// Gets the method name from the JSON-RPC message
422 ///
423 /// # Returns
424 ///
425 /// The method name if present (for requests and notifications), or None for responses
426 pub fn get_method(&self) -> Option<&str> {
427 match *self {
428 JsonRpc::Notification(ref v) => Some(&v.method),
429 JsonRpc::Request(ref v) => Some(&v.method),
430 _ => None,
431 }
432 }
433
434 /// Gets the parameters from the JSON-RPC message
435 ///
436 /// # Returns
437 ///
438 /// The parameters if present (for requests and notifications), or None for responses
439 pub fn get_params(&self) -> Option<Params> {
440 match *self {
441 JsonRpc::Notification(ref v) => v.params.as_ref().cloned(),
442 JsonRpc::Request(ref v) => v.params.as_ref().cloned(),
443 _ => None,
444 }
445 }
446
447 /// Gets the result from a successful JSON-RPC response
448 ///
449 /// # Returns
450 ///
451 /// The result if this is a success response, or None otherwise
452 pub fn get_result(&self) -> Option<&Value> {
453 match *self {
454 JsonRpc::Success(ref v) => Some(&v.result),
455 _ => None,
456 }
457 }
458
459 /// Gets the error from an error JSON-RPC response
460 ///
461 /// # Returns
462 ///
463 /// The error if this is an error response, or None otherwise
464 pub fn get_error(&self) -> Option<&RpcError> {
465 match *self {
466 JsonRpc::Error(ref v) => Some(&v.error),
467 _ => None,
468 }
469 }
470
471 /// Parses a JSON string into a JSON-RPC message
472 ///
473 /// # Arguments
474 ///
475 /// * `input` - The JSON string to parse
476 ///
477 /// # Returns
478 ///
479 /// A Result containing either the parsed JsonRpc or a serde_json error
480 ///
481 /// # Examples
482 ///
483 /// ```
484 /// use jsonrpc_lite::JsonRpc;
485 ///
486 /// let input = r#"{"jsonrpc":"2.0","method":"subtract","params":[42,23],"id":1}"#;
487 /// let request = JsonRpc::parse(input).unwrap();
488 /// ```
489 pub fn parse(input: &str) -> SerdeResult<Self> {
490 use serde_json::from_str;
491 from_str(input)
492 }
493
494 /// Parses a JSON string into a vector of JSON-RPC messages
495 ///
496 /// This is useful for batch requests and responses.
497 ///
498 /// # Arguments
499 ///
500 /// * `input` - The JSON string to parse
501 ///
502 /// # Returns
503 ///
504 /// A Result containing either a vector of parsed JsonRpc objects or a serde_json error
505 ///
506 /// # Examples
507 ///
508 /// ```
509 /// use jsonrpc_lite::JsonRpc;
510 ///
511 /// let input = r#"[{"jsonrpc":"2.0","method":"sum","params":[1,2,4],"id":"1"},{"jsonrpc":"2.0","method":"notify_hello","params":[7]}]"#;
512 /// let batch = JsonRpc::parse_vec(input).unwrap();
513 /// assert_eq!(batch.len(), 2);
514 /// ```
515 pub fn parse_vec(input: &str) -> SerdeResult<Vec<Self>> {
516 use serde_json::from_str;
517 from_str(input)
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524 use serde_json::{json, to_value};
525
526 #[test]
527 fn request() {
528 let jsonrpc = to_value(JsonRpc::request((), "test"))
529 .expect("Unable to turn request into a Json Value");
530 assert_eq!(
531 jsonrpc,
532 json!({
533 "id": null,
534 "jsonrpc": "2.0",
535 "method": "test"
536 })
537 );
538 }
539
540 #[test]
541 fn request_with_params_vec() {
542 let jsonrpc = to_value(JsonRpc::request_with_params(
543 46714,
544 "test",
545 json!([true, false, false, true]),
546 ))
547 .expect("Unable to turn request_with_params_vec into a Json Value");
548 assert_eq!(
549 jsonrpc,
550 json!({
551 "id": 46714,
552 "jsonrpc": "2.0",
553 "method": "test",
554 "params": [true, false, false, true]
555 })
556 );
557 }
558
559 #[test]
560 fn request_with_params_map() {
561 let jsonrpc = to_value(JsonRpc::request_with_params(
562 String::from("alpha-gamma-06714"),
563 "test",
564 json!({
565 "key": "94151351-5651651658-56151351351",
566 "n": 5158,
567 "mean": 454.54
568 }),
569 ))
570 .expect("Unable to turn request_with_params_map into a Json Value");
571 assert_eq!(
572 jsonrpc,
573 json!({
574 "id": "alpha-gamma-06714",
575 "jsonrpc": "2.0",
576 "method": "test",
577 "params": {
578 "key": "94151351-5651651658-56151351351",
579 "n": 5158,
580 "mean": 454.54
581 }
582 })
583 );
584 }
585}