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))]
20#[derive(
21 Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize, PartialOrd, Ord, Display, From,
22)]
23#[serde(untagged)]
24#[allow(
25 clippy::exhaustive_enums,
26 reason = "Request IDs are limited to JSON-RPC's non-null ID types"
27)]
28#[from(String, i64)]
29pub enum RequestId {
30 Number(i64),
32 Str(String),
34}
35
36#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
38#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
39#[allow(
40 clippy::exhaustive_structs,
41 reason = "This comes from the JSON-RPC specification itself"
42)]
43#[cfg_attr(feature = "schemars", schemars(rename = "{Params}", extend("x-docs-ignore" = true)))]
44#[skip_serializing_none]
45pub struct Request<Params> {
46 pub id: RequestId,
48 pub method: Arc<str>,
50 pub params: Option<Params>,
52}
53
54#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
56#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
57#[allow(
58 clippy::exhaustive_enums,
59 reason = "This comes from the JSON-RPC specification itself"
60)]
61#[serde(untagged)]
62#[cfg_attr(feature = "schemars", schemars(rename = "{Result}", extend("x-docs-ignore" = true)))]
63pub enum Response<Result> {
64 Result {
66 id: RequestId,
68 #[serde(
71 deserialize_with = "Deserialize::deserialize",
72 bound(deserialize = "Result: Deserialize<'de>")
73 )]
74 result: Result,
75 },
76 Error {
78 #[serde(deserialize_with = "Option::deserialize")]
84 #[cfg_attr(feature = "schemars", schemars(required, transform = nullable_request_id))]
85 id: Option<RequestId>,
86 error: Error,
88 },
89}
90
91#[cfg(feature = "schemars")]
94fn nullable_request_id(schema: &mut schemars::Schema) {
95 let null = serde_json::json!({ "type": "null" });
96 if let Some(variants) = schema
97 .get_mut("anyOf")
98 .and_then(serde_json::Value::as_array_mut)
99 && !variants.contains(&null)
100 {
101 variants.push(null);
102 }
103}
104
105impl<R> Response<R> {
106 #[must_use]
108 pub fn new(id: impl Into<RequestId>, result: std::result::Result<R, Error>) -> Self {
109 match result {
110 Ok(result) => Self::Result {
111 id: id.into(),
112 result,
113 },
114 Err(error) => Self::Error {
115 id: Some(id.into()),
116 error,
117 },
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 use serde_json::{Value, json};
127
128 #[test]
129 fn request_id_rejects_null() {
130 assert!(serde_json::from_value::<RequestId>(Value::Null).is_err());
131 assert!(
132 serde_json::from_value::<Request<Value>>(json!({ "id": null, "method": "m" })).is_err()
133 );
134 assert_eq!(
135 serde_json::from_value::<RequestId>(json!(-1)).unwrap(),
136 RequestId::Number(-1)
137 );
138 assert_eq!(
139 serde_json::from_value::<RequestId>(json!("id")).unwrap(),
140 RequestId::Str("id".to_owned())
141 );
142 }
143
144 #[test]
145 fn error_response_id_is_required_and_nullable() {
146 let error = json!({ "code": -32700, "message": "Parse error" });
147
148 let response =
149 serde_json::from_value::<Response<Value>>(json!({ "id": null, "error": error }))
150 .unwrap();
151 assert!(matches!(response, Response::Error { id: None, .. }));
152 assert_eq!(
153 serde_json::to_value(&response).unwrap(),
154 json!({ "id": null, "error": error })
155 );
156
157 assert!(serde_json::from_value::<Response<Value>>(json!({ "error": error })).is_err());
158 assert!(
159 serde_json::from_value::<Response<Value>>(json!({ "id": null, "result": {} })).is_err()
160 );
161 }
162}