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}