1use std::sync::Arc;
7
8use derive_more::{Display, From};
9use serde::{Deserialize, Serialize};
10use serde_with::skip_serializing_none;
11
12#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
22#[derive(
23 Debug, PartialEq, Clone, Hash, Eq, Deserialize, Serialize, PartialOrd, Ord, Display, From,
24)]
25#[serde(untagged)]
26#[allow(
27 clippy::exhaustive_enums,
28 reason = "This comes from the JSON-RPC specification itself"
29)]
30#[from(String, i64)]
31pub enum RequestId {
32 #[display("null")]
34 Null,
35 Number(i64),
37 Str(String),
39}
40
41#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
43#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
44#[allow(
45 clippy::exhaustive_structs,
46 reason = "This comes from the JSON-RPC specification itself"
47)]
48#[cfg_attr(feature = "schemars", schemars(rename = "{Params}", extend("x-docs-ignore" = true)))]
49#[skip_serializing_none]
50pub struct Request<Params> {
51 pub id: RequestId,
53 pub method: Arc<str>,
55 pub params: Option<Params>,
57}
58
59#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
61#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
62#[allow(
63 clippy::exhaustive_enums,
64 reason = "This comes from the JSON-RPC specification itself"
65)]
66#[serde(untagged)]
67#[cfg_attr(feature = "schemars", schemars(rename = "{Result}", extend("x-docs-ignore" = true)))]
68pub enum Response<Result, Error> {
69 Result {
71 id: RequestId,
73 result: Result,
75 },
76 Error {
78 id: RequestId,
80 error: Error,
82 },
83}
84
85impl<R, E> Response<R, E> {
86 #[must_use]
88 pub fn new(id: impl Into<RequestId>, result: std::result::Result<R, E>) -> Self {
89 match result {
90 Ok(result) => Self::Result {
91 id: id.into(),
92 result,
93 },
94 Err(error) => Self::Error {
95 id: id.into(),
96 error,
97 },
98 }
99 }
100}
101
102#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
104#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
105#[allow(
106 clippy::exhaustive_structs,
107 reason = "This comes from the JSON-RPC specification itself"
108)]
109#[cfg_attr(feature = "schemars", schemars(rename = "{Params}", extend("x-docs-ignore" = true)))]
110#[skip_serializing_none]
111pub struct Notification<Params> {
112 pub method: Arc<str>,
114 pub params: Option<Params>,
116}
117
118#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
120#[cfg_attr(feature = "schemars", schemars(inline))]
121enum JsonRpcVersion {
122 #[serde(rename = "2.0")]
123 V2,
124}
125
126#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132#[cfg_attr(feature = "schemars", schemars(inline))]
133pub struct JsonRpcMessage<M> {
134 jsonrpc: JsonRpcVersion,
135 #[serde(flatten)]
136 message: M,
137}
138
139impl<M> JsonRpcMessage<M> {
140 #[must_use]
142 pub fn wrap(message: M) -> Self {
143 Self {
144 jsonrpc: JsonRpcVersion::V2,
145 message,
146 }
147 }
148
149 #[must_use]
151 pub fn inner(&self) -> &M {
152 &self.message
153 }
154
155 #[must_use]
157 pub fn into_inner(self) -> M {
158 self.message
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
164#[display("JSON-RPC batch must contain at least one message")]
165#[non_exhaustive]
166pub struct EmptyJsonRpcBatch;
167
168impl std::error::Error for EmptyJsonRpcBatch {}
169
170#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
172#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
173#[cfg_attr(feature = "schemars", schemars(inline))]
174#[serde(transparent)]
175#[allow(
176 clippy::exhaustive_structs,
177 reason = "This comes from the JSON-RPC specification itself"
178)]
179pub struct JsonRpcBatch<M>(
180 #[cfg_attr(feature = "schemars", schemars(length(min = 1)))] Vec<JsonRpcMessage<M>>,
181);
182
183impl<M> JsonRpcBatch<M> {
184 pub fn new(messages: Vec<JsonRpcMessage<M>>) -> Result<Self, EmptyJsonRpcBatch> {
193 if messages.is_empty() {
194 Err(EmptyJsonRpcBatch)
195 } else {
196 Ok(Self(messages))
197 }
198 }
199
200 #[must_use]
202 pub fn as_slice(&self) -> &[JsonRpcMessage<M>] {
203 &self.0
204 }
205
206 #[must_use]
208 pub fn into_vec(self) -> Vec<JsonRpcMessage<M>> {
209 self.0
210 }
211}
212
213impl<M> TryFrom<Vec<JsonRpcMessage<M>>> for JsonRpcBatch<M> {
214 type Error = EmptyJsonRpcBatch;
215
216 fn try_from(messages: Vec<JsonRpcMessage<M>>) -> Result<Self, Self::Error> {
217 Self::new(messages)
218 }
219}
220
221impl<'de, M> Deserialize<'de> for JsonRpcBatch<M>
222where
223 M: Deserialize<'de>,
224{
225 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
226 where
227 D: serde::Deserializer<'de>,
228 {
229 let messages = Vec::<JsonRpcMessage<M>>::deserialize(deserializer)?;
230 Self::new(messages).map_err(serde::de::Error::custom)
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 use crate::v1::{
239 AgentNotification, CancelNotification, ClientNotification, ContentBlock, ContentChunk,
240 SessionId, SessionNotification, SessionUpdate, TextContent,
241 };
242 use serde_json::{Number, Value, json};
243
244 #[test]
245 fn id_deserialization() {
246 let id = serde_json::from_value::<RequestId>(Value::Null).unwrap();
247 assert_eq!(id, RequestId::Null);
248
249 let id = serde_json::from_value::<RequestId>(Value::Number(Number::from_u128(1).unwrap()))
250 .unwrap();
251 assert_eq!(id, RequestId::Number(1));
252
253 let id = serde_json::from_value::<RequestId>(Value::Number(Number::from_i128(-1).unwrap()))
254 .unwrap();
255 assert_eq!(id, RequestId::Number(-1));
256
257 let id = serde_json::from_value::<RequestId>(Value::String("id".to_owned())).unwrap();
258 assert_eq!(id, RequestId::Str("id".to_owned()));
259 }
260
261 #[test]
262 fn id_serialization() {
263 let id = serde_json::to_value(RequestId::Null).unwrap();
264 assert_eq!(id, Value::Null);
265
266 let id = serde_json::to_value(RequestId::Number(1)).unwrap();
267 assert_eq!(id, Value::Number(Number::from_u128(1).unwrap()));
268
269 let id = serde_json::to_value(RequestId::Number(-1)).unwrap();
270 assert_eq!(id, Value::Number(Number::from_i128(-1).unwrap()));
271
272 let id = serde_json::to_value(RequestId::Str("id".to_owned())).unwrap();
273 assert_eq!(id, Value::String("id".to_owned()));
274 }
275
276 #[test]
277 fn id_display() {
278 let id = RequestId::Null;
279 assert_eq!(id.to_string(), "null");
280
281 let id = RequestId::Number(1);
282 assert_eq!(id.to_string(), "1");
283
284 let id = RequestId::Number(-1);
285 assert_eq!(id.to_string(), "-1");
286
287 let id = RequestId::Str("id".to_owned());
288 assert_eq!(id.to_string(), "id");
289 }
290
291 #[test]
292 fn batch_deserialization_requires_at_least_one_message() {
293 let err = serde_json::from_value::<JsonRpcBatch<Notification<ClientNotification>>>(
294 Value::Array(Vec::new()),
295 )
296 .unwrap_err();
297 assert!(err.to_string().contains("at least one message"));
298 }
299
300 #[test]
301 fn batch_serialization_round_trips_non_empty_messages() {
302 let notification = JsonRpcMessage::wrap(Notification {
303 method: "cancel".into(),
304 params: Some(ClientNotification::CancelNotification(CancelNotification {
305 session_id: SessionId("test-123".into()),
306 meta: None,
307 })),
308 });
309
310 let batch = JsonRpcBatch::new(vec![notification]).unwrap();
311 let serialized = serde_json::to_value(&batch).unwrap();
312 assert_eq!(
313 serialized,
314 json!([{
315 "jsonrpc": "2.0",
316 "method": "cancel",
317 "params": {
318 "sessionId": "test-123"
319 },
320 }])
321 );
322
323 let deserialized =
324 serde_json::from_value::<JsonRpcBatch<Notification<ClientNotification>>>(serialized)
325 .unwrap();
326 assert_eq!(deserialized.as_slice().len(), 1);
327 assert_eq!(deserialized.as_slice()[0].inner().method.as_ref(), "cancel");
328 }
329
330 #[test]
331 fn notification_wire_format() {
332 let outgoing_msg = JsonRpcMessage::wrap(Notification {
334 method: "cancel".into(),
335 params: Some(ClientNotification::CancelNotification(CancelNotification {
336 session_id: SessionId("test-123".into()),
337 meta: None,
338 })),
339 });
340
341 let serialized: Value = serde_json::to_value(&outgoing_msg).unwrap();
342 assert_eq!(
343 serialized,
344 json!({
345 "jsonrpc": "2.0",
346 "method": "cancel",
347 "params": {
348 "sessionId": "test-123"
349 },
350 })
351 );
352
353 let outgoing_msg = JsonRpcMessage::wrap(Notification {
355 method: "sessionUpdate".into(),
356 params: Some(AgentNotification::SessionNotification(
357 SessionNotification {
358 session_id: SessionId("test-456".into()),
359 update: SessionUpdate::AgentMessageChunk(ContentChunk {
360 content: ContentBlock::Text(TextContent {
361 annotations: None,
362 text: "Hello".to_string(),
363 meta: None,
364 }),
365 message_id: None,
366 meta: None,
367 }),
368 meta: None,
369 },
370 )),
371 });
372
373 let serialized: Value = serde_json::to_value(&outgoing_msg).unwrap();
374 assert_eq!(
375 serialized,
376 json!({
377 "jsonrpc": "2.0",
378 "method": "sessionUpdate",
379 "params": {
380 "sessionId": "test-456",
381 "update": {
382 "sessionUpdate": "agent_message_chunk",
383 "content": {
384 "type": "text",
385 "text": "Hello"
386 }
387 }
388 }
389 })
390 );
391 }
392}