agent_client_protocol/jsonrpc/
transport_actor.rs1use std::pin::pin;
2
3use crate::RawJsonRpcResponse as Response;
5use crate::jsonrpc::{RawJsonRpcMessage, TransportFrame};
6use futures::StreamExt as _;
7use futures::channel::mpsc;
8
9fn parse_incoming_line(line: &str) -> TransportFrame {
10 let value = match serde_json::from_str::<serde_json::Value>(line) {
11 Ok(value) => value,
12 Err(error) => {
13 tracing::debug!(
15 category = ?error.classify(),
16 line = error.line(),
17 column = error.column(),
18 "Failed to parse incoming JSON-RPC JSON"
19 );
20 return TransportFrame::Malformed {
21 raw: line.to_owned(),
22 error: crate::Error::parse_error().data(serde_json::json!({ "line": line })),
23 };
24 }
25 };
26
27 match value {
28 serde_json::Value::Array(_) => TransportFrame::Malformed {
31 raw: line.to_owned(),
32 error: crate::util::batch_not_supported(),
33 },
34 value => match serde_json::from_value(value) {
35 Ok(message) => TransportFrame::Single(message),
36 Err(error) => {
37 tracing::debug!(category = ?error.classify(), "Invalid JSON-RPC message");
38 TransportFrame::Malformed {
39 raw: line.to_owned(),
40 error: crate::Error::invalid_request(),
41 }
42 }
43 },
44 }
45}
46
47impl TransportFrame {
48 #[must_use]
56 pub fn parse_json(input: &str) -> Self {
57 parse_incoming_line(input)
58 }
59
60 pub fn to_json(&self) -> Result<String, crate::Error> {
67 match self {
68 Self::Single(message) => {
69 serde_json::to_string(message).map_err(crate::Error::into_internal_error)
70 }
71 Self::Malformed { raw, .. } => Ok(raw.clone()),
72 }
73 }
74}
75
76async fn transport_outgoing_frames_actor(
90 transport_rx: impl futures::Stream<Item = TransportFrame>,
91 outgoing_lines: impl futures::Sink<String, Error = std::io::Error>,
92) -> Result<(), crate::Error> {
93 use futures::SinkExt;
94 let mut transport_rx = pin!(transport_rx);
95 let mut outgoing_lines = pin!(outgoing_lines);
96
97 while let Some(frame) = transport_rx.next().await {
98 let json_rpc_message = match frame {
99 TransportFrame::Single(message) => message,
100 TransportFrame::Malformed { raw, .. } => {
101 let raw = malformed_line_value(raw)?;
102 tracing::trace!(
103 message = %crate::util::redacted_json_line(&raw),
104 "Relaying invalid JSON-RPC value"
105 );
106 outgoing_lines
107 .send(raw)
108 .await
109 .map_err(crate::Error::into_internal_error)?;
110 continue;
111 }
112 };
113 match serde_json::to_string(&json_rpc_message) {
114 Ok(line) => {
115 tracing::trace!(message = %crate::util::redacted_json_line(&line), "Sending JSON-RPC message");
116 outgoing_lines
117 .send(line)
118 .await
119 .map_err(crate::Error::into_internal_error)?;
120 }
121
122 Err(serialization_error) => {
123 match json_rpc_message {
124 RawJsonRpcMessage::Request(_) | RawJsonRpcMessage::Notification(_) => {
125 tracing::error!(
130 ?serialization_error,
131 "Failed to serialize request, ignoring"
132 );
133 }
134 RawJsonRpcMessage::Response(response) => {
135 let id = match response {
138 Response::Result { id, .. } | Response::Error { id, .. } => id,
139 };
140 tracing::error!(
141 ?serialization_error,
142 ?id,
143 "Failed to serialize response, sending internal_error instead"
144 );
145 let error_line = serde_json::to_string(&RawJsonRpcMessage::response(
146 id,
147 Err(crate::Error::internal_error()),
148 ))
149 .unwrap();
150 outgoing_lines
151 .send(error_line)
152 .await
153 .map_err(crate::Error::into_internal_error)?;
154 }
155 }
156 }
157 }
158 }
159 outgoing_lines
160 .close()
161 .await
162 .map_err(crate::Error::into_internal_error)
163}
164
165pub(super) struct LineWriter<W> {
168 writer: std::pin::Pin<Box<W>>,
169 bytes: Vec<u8>,
170 written: usize,
171 closing: bool,
172}
173
174impl<W> LineWriter<W> {
175 pub(super) fn new(writer: W) -> Self {
176 Self {
177 writer: Box::pin(writer),
178 bytes: Vec::new(),
179 written: 0,
180 closing: false,
181 }
182 }
183}
184
185impl<W: futures::AsyncWrite> futures::Sink<String> for LineWriter<W> {
186 type Error = std::io::Error;
187
188 fn poll_ready(
189 self: std::pin::Pin<&mut Self>,
190 cx: &mut std::task::Context<'_>,
191 ) -> std::task::Poll<Result<(), Self::Error>> {
192 self.poll_flush(cx)
193 }
194
195 fn start_send(self: std::pin::Pin<&mut Self>, line: String) -> Result<(), Self::Error> {
196 let this = self.get_mut();
197 this.bytes = line.into_bytes();
198 this.bytes.push(b'\n');
199 this.written = 0;
200 Ok(())
201 }
202
203 fn poll_flush(
204 self: std::pin::Pin<&mut Self>,
205 cx: &mut std::task::Context<'_>,
206 ) -> std::task::Poll<Result<(), Self::Error>> {
207 let this = self.get_mut();
208 while this.written < this.bytes.len() {
209 let count = futures::ready!(
210 this.writer
211 .as_mut()
212 .poll_write(cx, &this.bytes[this.written..])
213 )?;
214 if count == 0 {
215 return std::task::Poll::Ready(Err(std::io::ErrorKind::WriteZero.into()));
216 }
217 this.written += count;
218 }
219 this.bytes.clear();
220 this.written = 0;
221 this.writer.as_mut().poll_flush(cx)
222 }
223
224 fn poll_close(
225 mut self: std::pin::Pin<&mut Self>,
226 cx: &mut std::task::Context<'_>,
227 ) -> std::task::Poll<Result<(), Self::Error>> {
228 if !self.closing {
229 futures::ready!(self.as_mut().poll_flush(cx))?;
230 self.closing = true;
231 }
232 self.get_mut().writer.as_mut().poll_close(cx)
233 }
234}
235
236fn malformed_line_value(raw: String) -> Result<String, crate::Error> {
237 if !raw.contains('\r') && !raw.contains('\n') {
238 return Ok(raw);
239 }
240
241 match serde_json::from_str::<serde_json::Value>(&raw) {
242 Ok(value) => serde_json::to_string(&value),
243 Err(_) => serde_json::to_string(&raw),
244 }
245 .map_err(crate::Error::into_internal_error)
246}
247
248pub(super) async fn transport_outgoing_lines_actor(
249 transport_rx: impl futures::Stream<Item = TransportFrame>,
250 outgoing_lines: impl futures::Sink<String, Error = std::io::Error>,
251) -> Result<(), crate::Error> {
252 transport_outgoing_frames_actor(transport_rx, outgoing_lines).await
253}
254
255pub(super) async fn transport_incoming_lines_actor(
269 incoming_lines: impl futures::Stream<Item = std::io::Result<String>>,
270 transport_tx: mpsc::UnboundedSender<TransportFrame>,
271) -> Result<(), crate::Error> {
272 let mut incoming_lines = pin!(incoming_lines);
273 while let Some(line_result) = incoming_lines.next().await {
274 let line = line_result.map_err(crate::Error::into_internal_error)?;
275 tracing::trace!(message = %crate::util::redacted_json_line(&line), "Received JSON-RPC message");
276
277 transport_tx
278 .unbounded_send(parse_incoming_line(&line))
279 .map_err(crate::Error::into_internal_error)?;
280 }
281 Ok(())
282}
283
284#[cfg(test)]
285mod tests {
286 use std::sync::{Arc, Mutex};
287
288 use super::*;
289 use crate::ErrorCode;
290
291 #[derive(Default)]
292 struct PendingCloseWriter {
293 bytes: Vec<u8>,
294 close_polls: usize,
295 }
296
297 impl futures::AsyncWrite for PendingCloseWriter {
298 fn poll_write(
299 mut self: std::pin::Pin<&mut Self>,
300 _: &mut std::task::Context<'_>,
301 bytes: &[u8],
302 ) -> std::task::Poll<std::io::Result<usize>> {
303 self.bytes.extend_from_slice(bytes);
304 std::task::Poll::Ready(Ok(bytes.len()))
305 }
306
307 fn poll_flush(
308 self: std::pin::Pin<&mut Self>,
309 _: &mut std::task::Context<'_>,
310 ) -> std::task::Poll<std::io::Result<()>> {
311 assert_eq!(self.close_polls, 0, "do not flush again during shutdown");
312 std::task::Poll::Ready(Ok(()))
313 }
314
315 fn poll_close(
316 mut self: std::pin::Pin<&mut Self>,
317 cx: &mut std::task::Context<'_>,
318 ) -> std::task::Poll<std::io::Result<()>> {
319 self.close_polls += 1;
320 if self.close_polls == 1 {
321 cx.waker().wake_by_ref();
322 std::task::Poll::Pending
323 } else {
324 std::task::Poll::Ready(Ok(()))
325 }
326 }
327 }
328
329 #[test]
330 fn byte_writer_flushes_then_continues_pending_shutdown_without_reflushing() {
331 use futures::SinkExt as _;
332 let mut sink = LineWriter::new(PendingCloseWriter::default());
333 futures::executor::block_on(async {
334 sink.send("line".to_string()).await.unwrap();
335 sink.close().await.unwrap();
336 });
337 assert_eq!(sink.writer.bytes, b"line\n");
338 assert_eq!(sink.writer.close_polls, 2);
339 }
340
341 #[test]
342 fn preserves_malformed_standalone_response() {
343 let TransportFrame::Malformed { error, .. } = parse_incoming_line(
344 r#"{"jsonrpc":"2.0","id":1,"result":null,"error":{"code":-32603,"message":"Internal error"}}"#,
345 ) else {
346 panic!("expected one retained invalid response");
347 };
348
349 assert_eq!(error.code, ErrorCode::INVALID_REQUEST);
350 }
351
352 #[test]
353 fn preserves_malformed_call_shaped_standalone_message() {
354 let TransportFrame::Malformed { error, .. } =
355 parse_incoming_line(r#"{"jsonrpc":"2.0","id":1,"method":"one","result":null}"#)
356 else {
357 panic!("expected one invalid-request error");
358 };
359
360 assert_eq!(error.code, ErrorCode::INVALID_REQUEST);
361 }
362
363 #[test]
364 fn parses_valid_standalone_response() {
365 assert!(matches!(
366 parse_incoming_line(r#"{"jsonrpc":"2.0","id":1,"result":{"ok":true}}"#),
367 TransportFrame::Single(RawJsonRpcMessage::Response(_))
368 ));
369 }
370
371 #[test]
372 fn batches_are_one_invalid_request() {
373 for line in [
374 "[]",
375 "[1, 2, 3]",
376 r#"[{"jsonrpc":"2.0","id":1,"method":"one","params":{}}]"#,
377 r#"[{"jsonrpc":"2.0","id":1,"result":null}]"#,
378 ] {
379 let TransportFrame::Malformed { raw, error } = parse_incoming_line(line) else {
380 panic!("expected one invalid-request error for {line}");
381 };
382 assert_eq!(raw, line);
383 assert_eq!(error.code, ErrorCode::INVALID_REQUEST);
384 }
385 }
386
387 #[test]
388 fn malformed_json_is_a_parse_error() {
389 let TransportFrame::Malformed { raw, error } = parse_incoming_line("[") else {
390 panic!("expected one parse error");
391 };
392
393 assert_eq!(raw, "[");
394 assert_eq!(error.code, ErrorCode::PARSE_ERROR);
395 }
396
397 #[test]
398 fn valid_json_with_an_invalid_envelope_is_an_invalid_request() {
399 let TransportFrame::Malformed { raw, error } = parse_incoming_line("17") else {
400 panic!("expected one invalid-request error");
401 };
402
403 assert_eq!(raw, "17");
404 assert_eq!(error.code, ErrorCode::INVALID_REQUEST);
405 }
406
407 #[tokio::test]
408 async fn multiline_malformed_frame_is_written_as_one_line_value() {
409 let raw = "not json\r\n{\"jsonrpc\":\"2.0\",\"method\":\"injected\"}".to_string();
410 let captured = Arc::new(Mutex::new(Vec::new()));
411 let outgoing = futures::sink::unfold(captured.clone(), |captured, line| async move {
412 captured.lock().unwrap().push(line);
413 Ok::<_, std::io::Error>(captured)
414 });
415
416 transport_outgoing_frames_actor(
417 futures::stream::iter([TransportFrame::Malformed {
418 raw: raw.clone(),
419 error: crate::Error::parse_error(),
420 }]),
421 outgoing,
422 )
423 .await
424 .unwrap();
425
426 let lines = captured.lock().unwrap();
427 assert_eq!(lines.len(), 1);
428 assert!(!lines[0].contains('\r') && !lines[0].contains('\n'));
429 assert_eq!(serde_json::from_str::<String>(&lines[0]).unwrap(), raw);
430 }
431
432 #[test]
433 fn multiline_invalid_json_rpc_value_is_compacted_without_changing_value() {
434 let raw = "{\n \"jsonrpc\": \"2.0\",\n \"method\": 1\n}".to_string();
435 let expected = serde_json::from_str::<serde_json::Value>(&raw).unwrap();
436 let line = malformed_line_value(raw).unwrap();
437
438 assert!(!line.contains('\r') && !line.contains('\n'));
439 assert_eq!(
440 serde_json::from_str::<serde_json::Value>(&line).unwrap(),
441 expected
442 );
443 }
444}