agent_client_protocol_schema/v2/
rpc.rs1use std::sync::Arc;
7
8use derive_more::{Display, From};
9use serde::{Deserialize, Serialize};
10use serde_with::skip_serializing_none;
11
12use super::Error;
13
14#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
18#[derive(
19 Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize, PartialOrd, Ord, Display, From,
20)]
21#[serde(untagged)]
22#[allow(
23 clippy::exhaustive_enums,
24 reason = "Request IDs are limited to JSON-RPC's non-null ID types"
25)]
26#[from(String, i64)]
27pub enum RequestId {
28 Number(i64),
30 Str(String),
32}
33
34#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
36#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
37#[allow(
38 clippy::exhaustive_structs,
39 reason = "This comes from the JSON-RPC specification itself"
40)]
41#[cfg_attr(feature = "schemars", schemars(rename = "{Params}", extend("x-docs-ignore" = true)))]
42#[skip_serializing_none]
43pub struct Request<Params> {
44 pub id: RequestId,
46 pub method: Arc<str>,
48 pub params: Option<Params>,
50}
51
52#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
54#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
55#[allow(
56 clippy::exhaustive_enums,
57 reason = "This comes from the JSON-RPC specification itself"
58)]
59#[serde(untagged)]
60#[cfg_attr(feature = "schemars", schemars(rename = "{Result}", extend("x-docs-ignore" = true)))]
61pub enum Response<Result> {
62 Result {
64 id: RequestId,
66 #[serde(
69 deserialize_with = "Deserialize::deserialize",
70 bound(deserialize = "Result: Deserialize<'de>")
71 )]
72 result: Result,
73 },
74 Error {
76 #[serde(deserialize_with = "Option::deserialize")]
82 #[cfg_attr(feature = "schemars", schemars(required))]
83 id: Option<RequestId>,
84 error: Error,
86 },
87}
88
89impl<R> Response<R> {
90 #[must_use]
92 pub fn new(id: impl Into<RequestId>, result: std::result::Result<R, Error>) -> Self {
93 match result {
94 Ok(result) => Self::Result {
95 id: id.into(),
96 result,
97 },
98 Err(error) => Self::Error {
99 id: Some(id.into()),
100 error,
101 },
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 use serde_json::{Value, json};
111
112 #[test]
113 fn request_id_rejects_null() {
114 assert!(serde_json::from_value::<RequestId>(Value::Null).is_err());
115 assert!(
116 serde_json::from_value::<Request<Value>>(json!({ "id": null, "method": "m" })).is_err()
117 );
118 assert_eq!(
119 serde_json::from_value::<RequestId>(json!(-1)).unwrap(),
120 RequestId::Number(-1)
121 );
122 assert_eq!(
123 serde_json::from_value::<RequestId>(json!("id")).unwrap(),
124 RequestId::Str("id".to_owned())
125 );
126 }
127
128 #[test]
129 fn error_response_id_is_required_and_nullable() {
130 let error = json!({ "code": -32700, "message": "Parse error" });
131
132 let response =
133 serde_json::from_value::<Response<Value>>(json!({ "id": null, "error": error }))
134 .unwrap();
135 assert!(matches!(response, Response::Error { id: None, .. }));
136 assert_eq!(
137 serde_json::to_value(&response).unwrap(),
138 json!({ "id": null, "error": error })
139 );
140
141 assert!(serde_json::from_value::<Response<Value>>(json!({ "error": error })).is_err());
142 assert!(
143 serde_json::from_value::<Response<Value>>(json!({ "id": null, "result": {} })).is_err()
144 );
145 }
146}