agent_client_protocol/jsonrpc/
raw_error.rs1use agent_client_protocol_schema::MaybeUndefined;
4use serde::{Deserialize, Serialize};
5use serde_json::{Map, Value};
6
7use super::RawJsonRpcId;
8
9#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[non_exhaustive]
16pub struct RawJsonRpcError {
17 pub code: i32,
19 pub message: String,
21 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
23 pub data: MaybeUndefined<Value>,
24 #[serde(flatten)]
26 pub extra: Map<String, Value>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(untagged)]
35#[allow(
36 clippy::exhaustive_enums,
37 reason = "JSON-RPC defines exactly these response kinds"
38)]
39pub enum RawJsonRpcResponse {
40 Result {
42 id: RawJsonRpcId,
44 #[serde(deserialize_with = "Deserialize::deserialize")]
47 result: Value,
48 },
49 Error {
51 id: RawJsonRpcId,
54 error: Box<RawJsonRpcError>,
56 },
57}
58
59impl RawJsonRpcResponse {
60 #[must_use]
62 pub fn new(id: impl Into<RawJsonRpcId>, result: Result<Value, Box<RawJsonRpcError>>) -> Self {
63 let id = id.into();
64 match result {
65 Ok(result) => Self::Result { id, result },
66 Err(error) => Self::Error { id, error },
67 }
68 }
69
70 #[must_use]
72 pub fn id(&self) -> &RawJsonRpcId {
73 match self {
74 Self::Result { id, .. } | Self::Error { id, .. } => id,
75 }
76 }
77}
78
79impl RawJsonRpcError {
80 #[must_use]
82 pub fn new(code: i32, message: impl Into<String>) -> Self {
83 Self {
84 code,
85 message: message.into(),
86 data: MaybeUndefined::Undefined,
87 extra: Map::new(),
88 }
89 }
90
91 #[must_use]
93 pub fn data(mut self, data: Value) -> Self {
94 self.data = if data.is_null() {
95 MaybeUndefined::Null
96 } else {
97 MaybeUndefined::Value(data)
98 };
99 self
100 }
101
102 #[must_use]
108 pub fn into_acp_error(self) -> crate::Error {
109 let mut error = crate::Error::new(self.code, self.message);
110 error.data = match self.data {
111 MaybeUndefined::Undefined => None,
112 MaybeUndefined::Null => Some(Value::Null),
113 MaybeUndefined::Value(data) => Some(data),
114 };
115 error
116 }
117}
118
119impl From<crate::Error> for RawJsonRpcError {
120 fn from(error: crate::Error) -> Self {
121 let raw = Self::new(error.code.into(), error.message);
122 match error.data {
123 Some(data) => raw.data(data),
124 None => raw,
125 }
126 }
127}
128
129impl std::fmt::Display for RawJsonRpcError {
130 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 write!(formatter, "{} ({})", self.message, self.code)
132 }
133}
134
135impl std::error::Error for RawJsonRpcError {}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use crate::{Channel, RawJsonRpcMessage, TransportFrame};
141 use futures::StreamExt as _;
142 use serde_json::json;
143
144 #[test]
145 fn raw_errors_preserve_codes_data_presence_and_extensions() {
146 for code in [i32::MIN, -32000, -32602, 0, 12345, i32::MAX] {
147 for data in [None, Some(Value::Null), Some(json!({"detail":[1,2]}))] {
148 let mut error = json!({
149 "code": code,
150 "message": "peer",
151 "extension": {"retry": true},
152 "_meta": {"opaque": "kept"}
153 });
154 if let Some(data) = &data {
155 error["data"] = data.clone();
156 }
157 let wire = json!({"jsonrpc":"2.0", "id":"logical", "error":error});
158 let parsed: RawJsonRpcMessage = serde_json::from_value(wire.clone()).unwrap();
159 assert_eq!(serde_json::to_value(&parsed).unwrap(), wire);
160 let RawJsonRpcMessage::Response(RawJsonRpcResponse::Error { error, .. }) = parsed
161 else {
162 panic!("expected a raw error response");
163 };
164 assert_eq!(error.code, code);
165 match data {
166 None => assert!(error.data.is_undefined()),
167 Some(Value::Null) => assert!(error.data.is_null()),
168 Some(value) => assert_eq!(error.data.value(), Some(&value)),
169 }
170 }
171 }
172 }
173
174 #[tokio::test]
175 async fn raw_responses_survive_framing_and_channel_relay() {
176 let error = json!({"code":-32000, "message":"peer", "extension":{"retry":true}});
177 for wire in [
178 json!({"jsonrpc":"2.0", "id":"error", "error":error}),
179 json!([
180 {"jsonrpc":"2.0", "id":"omitted", "error":error},
181 {"jsonrpc":"2.0", "id":"null", "error":{
182 "code":12345, "message":"peer", "data":null, "_meta":{"opaque":"kept"}
183 }},
184 {"jsonrpc":"2.0", "id":"success", "result":null}
185 ]),
186 ] {
187 let frame = TransportFrame::parse_json(&wire.to_string());
188 let (source, relay_in) = Channel::duplex();
189 let (relay_out, mut destination) = Channel::duplex();
190 source.tx.unbounded_send(frame).unwrap();
191 drop(source);
192 Channel {
193 rx: relay_in.rx,
194 tx: relay_out.tx,
195 }
196 .copy()
197 .await
198 .unwrap();
199 let received = destination.rx.next().await.unwrap();
200 let received: Value = serde_json::from_str(&received.to_json().unwrap()).unwrap();
201 assert_eq!(received, wire);
202 }
203 }
204
205 #[test]
206 fn acp_error_interpretation_is_explicit_and_keeps_data_presence() {
207 for (code, expected_code) in [
208 (-32000, crate::ErrorCode::AUTH_REQUIRED),
209 (12345, crate::ErrorCode::new(12345)),
210 ] {
211 for data in [None, Some(Value::Null), Some(json!({"detail":"kept"}))] {
212 let mut raw = RawJsonRpcError::new(code, "peer");
213 raw.extra.insert("extension".into(), json!(true));
214 if let Some(data) = &data {
215 raw = raw.data(data.clone());
216 }
217 let error = raw.into_acp_error();
218 assert_eq!(error.code, expected_code);
219 assert_eq!(error.message, "peer");
220 assert_eq!(error.data, data);
221 let roundtrip = RawJsonRpcError::from(error);
222 assert_eq!(roundtrip.code, code);
223 assert!(roundtrip.extra.is_empty());
224 match data {
225 None => assert!(roundtrip.data.is_undefined()),
226 Some(Value::Null) => assert!(roundtrip.data.is_null()),
227 Some(value) => assert_eq!(roundtrip.data.value(), Some(&value)),
228 }
229 }
230 }
231 }
232
233 #[test]
234 fn acp_response_constructor_preserves_explicit_null_data() {
235 for data in [None, Some(Value::Null), Some(json!({"detail":"kept"}))] {
236 let mut error = crate::Error::invalid_params();
237 error.data = data.clone();
238 let message = RawJsonRpcMessage::response(RawJsonRpcId::Null, Err(error));
239 let wire = serde_json::to_value(message).unwrap();
240 assert_eq!(wire["error"]["code"], -32602);
241 assert_eq!(wire["error"].get("data"), data.as_ref());
242 }
243 }
244
245 #[test]
246 fn malformed_raw_errors_are_still_rejected() {
247 for error in [
248 Value::Null,
249 json!({"code":-32000}),
250 json!({"message":"peer"}),
251 json!({"code":null, "message":"peer"}),
252 json!({"code":"-32000", "message":"peer"}),
253 json!({"code":1.5, "message":"peer"}),
254 json!({"code":-32000, "message":null}),
255 ] {
256 assert!(
257 serde_json::from_value::<RawJsonRpcMessage>(
258 json!({"jsonrpc":"2.0", "id":1, "error":error})
259 )
260 .is_err()
261 );
262 }
263 }
264}