agent_client_protocol/jsonrpc/
raw_error.rs1use agent_client_protocol_schema::MaybeUndefined;
4use serde::{Deserialize, Serialize};
5use serde_json::{Map, Value};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13#[non_exhaustive]
14pub struct RawJsonRpcError {
15 pub code: i32,
17 pub message: String,
19 #[serde(default, skip_serializing_if = "MaybeUndefined::is_undefined")]
21 pub data: MaybeUndefined<Value>,
22 #[serde(flatten)]
24 pub extra: Map<String, Value>,
25}
26
27pub type RawJsonRpcResponse =
32 agent_client_protocol_schema::rpc::Response<Value, Box<RawJsonRpcError>>;
33
34impl RawJsonRpcError {
35 #[must_use]
37 pub fn new(code: i32, message: impl Into<String>) -> Self {
38 Self {
39 code,
40 message: message.into(),
41 data: MaybeUndefined::Undefined,
42 extra: Map::new(),
43 }
44 }
45
46 #[must_use]
48 pub fn data(mut self, data: Value) -> Self {
49 self.data = if data.is_null() {
50 MaybeUndefined::Null
51 } else {
52 MaybeUndefined::Value(data)
53 };
54 self
55 }
56
57 #[must_use]
63 pub fn into_acp_error(self) -> crate::Error {
64 let mut error = crate::Error::new(self.code, self.message);
65 error.data = match self.data {
66 MaybeUndefined::Undefined => None,
67 MaybeUndefined::Null => Some(Value::Null),
68 MaybeUndefined::Value(data) => Some(data),
69 };
70 error
71 }
72}
73
74impl From<crate::Error> for RawJsonRpcError {
75 fn from(error: crate::Error) -> Self {
76 let raw = Self::new(error.code.into(), error.message);
77 match error.data {
78 Some(data) => raw.data(data),
79 None => raw,
80 }
81 }
82}
83
84impl std::fmt::Display for RawJsonRpcError {
85 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 write!(formatter, "{} ({})", self.message, self.code)
87 }
88}
89
90impl std::error::Error for RawJsonRpcError {}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::{Channel, RawJsonRpcMessage, TransportFrame};
96 use futures::StreamExt as _;
97 use serde_json::json;
98
99 #[test]
100 fn raw_errors_preserve_codes_data_presence_and_extensions() {
101 for code in [i32::MIN, -32000, -32602, 0, 12345, i32::MAX] {
102 for data in [None, Some(Value::Null), Some(json!({"detail":[1,2]}))] {
103 let mut error = json!({
104 "code": code,
105 "message": "peer",
106 "extension": {"retry": true},
107 "_meta": {"opaque": "kept"}
108 });
109 if let Some(data) = &data {
110 error["data"] = data.clone();
111 }
112 let wire = json!({"jsonrpc":"2.0", "id":"logical", "error":error});
113 let parsed: RawJsonRpcMessage = serde_json::from_value(wire.clone()).unwrap();
114 assert_eq!(serde_json::to_value(&parsed).unwrap(), wire);
115 let RawJsonRpcMessage::Response(RawJsonRpcResponse::Error { error, .. }) = parsed
116 else {
117 panic!("expected a raw error response");
118 };
119 assert_eq!(error.code, code);
120 match data {
121 None => assert!(error.data.is_undefined()),
122 Some(Value::Null) => assert!(error.data.is_null()),
123 Some(value) => assert_eq!(error.data.value(), Some(&value)),
124 }
125 }
126 }
127 }
128
129 #[tokio::test]
130 async fn raw_responses_survive_framing_and_channel_relay() {
131 let error = json!({"code":-32000, "message":"peer", "extension":{"retry":true}});
132 for wire in [
133 json!({"jsonrpc":"2.0", "id":"error", "error":error}),
134 json!([
135 {"jsonrpc":"2.0", "id":"omitted", "error":error},
136 {"jsonrpc":"2.0", "id":"null", "error":{
137 "code":12345, "message":"peer", "data":null, "_meta":{"opaque":"kept"}
138 }},
139 {"jsonrpc":"2.0", "id":"success", "result":null}
140 ]),
141 ] {
142 let frame = TransportFrame::parse_json(&wire.to_string());
143 let (source, relay_in) = Channel::duplex();
144 let (relay_out, mut destination) = Channel::duplex();
145 source.tx.unbounded_send(frame).unwrap();
146 drop(source);
147 Channel {
148 rx: relay_in.rx,
149 tx: relay_out.tx,
150 }
151 .copy()
152 .await
153 .unwrap();
154 let received = destination.rx.next().await.unwrap();
155 let received: Value = serde_json::from_str(&received.to_json().unwrap()).unwrap();
156 assert_eq!(received, wire);
157 }
158 }
159
160 #[test]
161 fn acp_error_interpretation_is_explicit_and_keeps_data_presence() {
162 for (code, expected_code) in [
163 (-32000, crate::ErrorCode::AuthRequired),
164 (12345, crate::ErrorCode::Other(12345)),
165 ] {
166 for data in [None, Some(Value::Null), Some(json!({"detail":"kept"}))] {
167 let mut raw = RawJsonRpcError::new(code, "peer");
168 raw.extra.insert("extension".into(), json!(true));
169 if let Some(data) = &data {
170 raw = raw.data(data.clone());
171 }
172 let error = raw.into_acp_error();
173 assert_eq!(error.code, expected_code);
174 assert_eq!(error.message, "peer");
175 assert_eq!(error.data, data);
176 let roundtrip = RawJsonRpcError::from(error);
177 assert_eq!(roundtrip.code, code);
178 assert!(roundtrip.extra.is_empty());
179 match data {
180 None => assert!(roundtrip.data.is_undefined()),
181 Some(Value::Null) => assert!(roundtrip.data.is_null()),
182 Some(value) => assert_eq!(roundtrip.data.value(), Some(&value)),
183 }
184 }
185 }
186 }
187
188 #[test]
189 fn acp_response_constructor_preserves_explicit_null_data() {
190 for data in [None, Some(Value::Null), Some(json!({"detail":"kept"}))] {
191 let mut error = crate::Error::invalid_params();
192 error.data = data.clone();
193 let message =
194 RawJsonRpcMessage::response(crate::schema::v1::RequestId::Null, Err(error));
195 let wire = serde_json::to_value(message).unwrap();
196 assert_eq!(wire["error"]["code"], -32602);
197 assert_eq!(wire["error"].get("data"), data.as_ref());
198 }
199 }
200
201 #[test]
202 fn malformed_raw_errors_are_still_rejected() {
203 for error in [
204 Value::Null,
205 json!({"code":-32000}),
206 json!({"message":"peer"}),
207 json!({"code":null, "message":"peer"}),
208 json!({"code":"-32000", "message":"peer"}),
209 json!({"code":1.5, "message":"peer"}),
210 json!({"code":-32000, "message":null}),
211 ] {
212 assert!(
213 serde_json::from_value::<RawJsonRpcMessage>(
214 json!({"jsonrpc":"2.0", "id":1, "error":error})
215 )
216 .is_err()
217 );
218 }
219 }
220}