aion_integrations/jsonrpc/
transport.rs1use std::sync::atomic::{AtomicU64, Ordering};
11
12use serde::Serialize;
13use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, Lines};
14use tokio::sync::Mutex;
15
16use super::envelope::{
17 IncomingMessage, JsonRpcId, JsonRpcNotification, JsonRpcRequest, JsonRpcResponse,
18};
19use crate::error::HarnessError;
20
21pub struct JsonRpcConnection<R, W> {
29 reader: Mutex<Lines<BufReader<R>>>,
30 writer: Mutex<W>,
31 next_id: AtomicU64,
32}
33
34impl<R, W> JsonRpcConnection<R, W>
35where
36 R: AsyncRead + Unpin + Send,
37 W: AsyncWrite + Unpin + Send,
38{
39 #[must_use]
41 pub fn new(read_half: R, write_half: W) -> Self {
42 Self {
43 reader: Mutex::new(BufReader::new(read_half).lines()),
44 writer: Mutex::new(write_half),
45 next_id: AtomicU64::new(1),
46 }
47 }
48
49 #[must_use]
54 pub fn next_request_id(&self) -> JsonRpcId {
55 JsonRpcId::number(self.next_id.fetch_add(1, Ordering::Relaxed))
56 }
57
58 pub async fn send_request(&self, request: &JsonRpcRequest) -> Result<(), HarnessError> {
65 self.write_frame(request).await
66 }
67
68 pub async fn send_notification(
75 &self,
76 notification: &JsonRpcNotification,
77 ) -> Result<(), HarnessError> {
78 self.write_frame(notification).await
79 }
80
81 pub async fn send_response(&self, response: &JsonRpcResponse) -> Result<(), HarnessError> {
88 self.write_frame(response).await
89 }
90
91 pub async fn recv(&self) -> Result<Option<IncomingMessage>, HarnessError> {
101 let mut reader = self.reader.lock().await;
102 loop {
103 let line = reader
104 .next_line()
105 .await
106 .map_err(|source| HarnessError::transport(format!("read failed: {source}")))?;
107 let Some(line) = line else {
108 return Ok(None);
109 };
110 let trimmed = line.trim();
111 if trimmed.is_empty() {
112 continue;
113 }
114 let value: serde_json::Value = serde_json::from_str(trimmed).map_err(|source| {
115 HarnessError::protocol(format!("invalid JSON frame: {source}"))
116 })?;
117 let message = IncomingMessage::from_value(value).map_err(|source| {
118 HarnessError::protocol(format!("frame is not a JSON-RPC message: {source}"))
119 })?;
120 return Ok(Some(message));
121 }
122 }
123
124 async fn write_frame<T: Serialize>(&self, frame: &T) -> Result<(), HarnessError> {
126 let mut encoded = serde_json::to_vec(frame).map_err(|source| {
127 HarnessError::protocol(format!("frame cannot be encoded: {source}"))
128 })?;
129 encoded.push(b'\n');
130 let mut writer = self.writer.lock().await;
131 writer
132 .write_all(&encoded)
133 .await
134 .map_err(|source| HarnessError::transport(format!("write failed: {source}")))?;
135 writer
136 .flush()
137 .await
138 .map_err(|source| HarnessError::transport(format!("flush failed: {source}")))?;
139 Ok(())
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use tokio::io::duplex;
146
147 use super::super::envelope::{IncomingMessage, JsonRpcNotification, JsonRpcRequest};
148 use super::JsonRpcConnection;
149 use crate::error::HarnessError;
150
151 #[tokio::test]
152 async fn request_and_notification_round_trip_over_a_duplex() -> Result<(), HarnessError> {
153 let (host_io, child_io) = duplex(4096);
155 let (host_read, host_write) = tokio::io::split(host_io);
156 let (child_read, child_write) = tokio::io::split(child_io);
157 let host = JsonRpcConnection::new(host_read, host_write);
158 let child = JsonRpcConnection::new(child_read, child_write);
159
160 let id = host.next_request_id();
161 let request = JsonRpcRequest::new(id.clone(), "run/execute", None);
162 host.send_request(&request).await?;
163
164 let received = child
165 .recv()
166 .await?
167 .ok_or_else(|| HarnessError::protocol("expected a frame, got end-of-stream"))?;
168 assert!(
169 matches!(&received, IncomingMessage::Request(got) if got.id == id && got.method == "run/execute"),
170 "expected the id-matched run/execute request, got {received:?}"
171 );
172
173 let notification = JsonRpcNotification::new("event/stop", None);
175 child.send_notification(¬ification).await?;
176 let echoed = host
177 .recv()
178 .await?
179 .ok_or_else(|| HarnessError::protocol("expected a frame, got end-of-stream"))?;
180 assert!(
181 matches!(echoed, IncomingMessage::Notification(_)),
182 "a frame with no id must classify as a notification"
183 );
184 Ok(())
185 }
186
187 #[tokio::test]
188 async fn ids_are_monotonic() {
189 let (a, _b) = duplex(64);
190 let (read, write) = tokio::io::split(a);
191 let connection = JsonRpcConnection::new(read, write);
192 let first = connection.next_request_id();
193 let second = connection.next_request_id();
194 assert_ne!(first, second);
195 }
196
197 #[tokio::test]
198 async fn recv_returns_none_at_end_of_stream() -> Result<(), HarnessError> {
199 let (host_io, child_io) = duplex(64);
200 let (host_read, host_write) = tokio::io::split(host_io);
201 let host = JsonRpcConnection::new(host_read, host_write);
202 drop(child_io);
204 let end = host.recv().await?;
205 assert!(end.is_none(), "closed stream yields None");
206 Ok(())
207 }
208}