Skip to main content

agent_client_protocol_schema/v1/
rpc.rs

1//! JSON-RPC request and response envelopes for protocol version 1.
2
3use std::sync::Arc;
4
5use derive_more::{Display, From};
6use serde::{Deserialize, Serialize};
7use serde_with::skip_serializing_none;
8
9/// JSON RPC Request Id
10///
11/// An identifier established by the Client that MUST contain a String, Number, or NULL value if included. If it is not included it is assumed to be a notification. The value SHOULD normally not be Null \[1\] and Numbers SHOULD NOT contain fractional parts \[2\]
12///
13/// The Server MUST reply with the same value in the Response object if included. This member is used to correlate the context between the two objects.
14///
15/// \[1\] The use of Null as a value for the id member in a Request object is discouraged, because this specification uses a value of Null for Responses with an unknown id. Also, because JSON-RPC 1.0 uses an id value of Null for Notifications this could cause confusion in handling.
16///
17/// \[2\] Fractional parts may be problematic, since many decimal fractions cannot be represented exactly as binary fractions.
18#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
19#[derive(
20    Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize, PartialOrd, Ord, Display, From,
21)]
22#[serde(untagged)]
23#[allow(
24    clippy::exhaustive_enums,
25    reason = "This comes from the JSON-RPC specification itself"
26)]
27#[from(String, i64)]
28pub enum RequestId {
29    /// The JSON-RPC `null` request id.
30    #[display("null")]
31    Null,
32    /// A numeric JSON-RPC request id.
33    Number(i64),
34    /// A string JSON-RPC request id.
35    Str(String),
36}
37
38/// A JSON-RPC request object.
39#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
40#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
41#[allow(
42    clippy::exhaustive_structs,
43    reason = "This comes from the JSON-RPC specification itself"
44)]
45#[cfg_attr(feature = "schemars", schemars(rename = "{Params}", extend("x-docs-ignore" = true)))]
46#[skip_serializing_none]
47pub struct Request<Params> {
48    /// The request id used to correlate the matching response.
49    pub id: RequestId,
50    /// The method name to invoke.
51    pub method: Arc<str>,
52    /// Method-specific request parameters.
53    pub params: Option<Params>,
54}
55
56/// A JSON-RPC response object.
57#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
58#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
59#[allow(
60    clippy::exhaustive_enums,
61    reason = "This comes from the JSON-RPC specification itself"
62)]
63#[serde(untagged)]
64#[cfg_attr(feature = "schemars", schemars(rename = "{Result}", extend("x-docs-ignore" = true)))]
65pub enum Response<Result, Error = super::Error> {
66    /// A successful JSON-RPC response.
67    Result {
68        /// The id of the request this response answers.
69        id: RequestId,
70        /// Method-specific response data.
71        // Require the key even when the payload's deserializer accepts null.
72        #[serde(
73            deserialize_with = "Deserialize::deserialize",
74            bound(deserialize = "Result: Deserialize<'de>")
75        )]
76        result: Result,
77    },
78    /// A failed JSON-RPC response.
79    Error {
80        /// The id of the request this response answers.
81        id: RequestId,
82        /// Method-specific error data.
83        error: Error,
84    },
85}
86
87impl<R, E> Response<R, E> {
88    /// Creates a JSON-RPC response from a Rust [`Result`].
89    #[must_use]
90    pub fn new(id: impl Into<RequestId>, result: std::result::Result<R, E>) -> Self {
91        match result {
92            Ok(result) => Self::Result {
93                id: id.into(),
94                result,
95            },
96            Err(error) => Self::Error {
97                id: id.into(),
98                error,
99            },
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    use serde_json::{Number, Value};
109
110    #[test]
111    fn id_deserialization() {
112        let id = serde_json::from_value::<RequestId>(Value::Null).unwrap();
113        assert_eq!(id, RequestId::Null);
114
115        let id = serde_json::from_value::<RequestId>(Value::Number(Number::from_u128(1).unwrap()))
116            .unwrap();
117        assert_eq!(id, RequestId::Number(1));
118
119        let id = serde_json::from_value::<RequestId>(Value::Number(Number::from_i128(-1).unwrap()))
120            .unwrap();
121        assert_eq!(id, RequestId::Number(-1));
122
123        let id = serde_json::from_value::<RequestId>(Value::String("id".to_owned())).unwrap();
124        assert_eq!(id, RequestId::Str("id".to_owned()));
125    }
126
127    #[test]
128    fn id_serialization() {
129        let id = serde_json::to_value(RequestId::Null).unwrap();
130        assert_eq!(id, Value::Null);
131
132        let id = serde_json::to_value(RequestId::Number(1)).unwrap();
133        assert_eq!(id, Value::Number(Number::from_u128(1).unwrap()));
134
135        let id = serde_json::to_value(RequestId::Number(-1)).unwrap();
136        assert_eq!(id, Value::Number(Number::from_i128(-1).unwrap()));
137
138        let id = serde_json::to_value(RequestId::Str("id".to_owned())).unwrap();
139        assert_eq!(id, Value::String("id".to_owned()));
140    }
141
142    #[test]
143    fn id_display() {
144        let id = RequestId::Null;
145        assert_eq!(id.to_string(), "null");
146
147        let id = RequestId::Number(1);
148        assert_eq!(id.to_string(), "1");
149
150        let id = RequestId::Number(-1);
151        assert_eq!(id.to_string(), "-1");
152
153        let id = RequestId::Str("id".to_owned());
154        assert_eq!(id.to_string(), "id");
155    }
156}