things3_cli/mcp/
io_wrapper.rs1use async_trait::async_trait;
8use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, DuplexStream};
9
10#[async_trait]
15pub trait McpIo: Send + Sync {
16 async fn read_line(&mut self) -> std::io::Result<Option<String>>;
21
22 async fn write_line(&mut self, line: &str) -> std::io::Result<()>;
26
27 async fn flush(&mut self) -> std::io::Result<()>;
29}
30
31pub struct StdIo {
33 reader: BufReader<tokio::io::Stdin>,
34 writer: tokio::io::Stdout,
35 buffer: String,
36}
37
38impl StdIo {
39 pub fn new() -> Self {
41 Self {
42 reader: BufReader::new(tokio::io::stdin()),
43 writer: tokio::io::stdout(),
44 buffer: String::new(),
45 }
46 }
47}
48
49impl Default for StdIo {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55#[async_trait]
56impl McpIo for StdIo {
57 async fn read_line(&mut self) -> std::io::Result<Option<String>> {
58 self.buffer.clear();
59 let bytes_read = self.reader.read_line(&mut self.buffer).await?;
60
61 if bytes_read == 0 {
62 Ok(None) } else {
64 Ok(Some(self.buffer.trim().to_string()))
65 }
66 }
67
68 async fn write_line(&mut self, line: &str) -> std::io::Result<()> {
69 self.writer.write_all(line.as_bytes()).await?;
70 self.writer.write_all(b"\n").await?;
71 Ok(())
72 }
73
74 async fn flush(&mut self) -> std::io::Result<()> {
75 self.writer.flush().await
76 }
77}
78
79pub struct MockIo {
81 reader: BufReader<tokio::io::ReadHalf<DuplexStream>>,
82 writer: tokio::io::WriteHalf<DuplexStream>,
83 buffer: String,
84}
85
86impl MockIo {
87 pub fn new(stream: DuplexStream) -> Self {
92 let (read_half, write_half) = tokio::io::split(stream);
93 Self {
94 reader: BufReader::new(read_half),
95 writer: write_half,
96 buffer: String::new(),
97 }
98 }
99
100 pub fn create_pair(buffer_size: usize) -> (Self, Self) {
106 let (client_stream, server_stream) = tokio::io::duplex(buffer_size);
107 let server_io = Self::new(server_stream);
108 let client_io = Self::new(client_stream);
109 (server_io, client_io)
110 }
111}
112
113#[async_trait]
114impl McpIo for MockIo {
115 async fn read_line(&mut self) -> std::io::Result<Option<String>> {
116 self.buffer.clear();
117 let bytes_read = self.reader.read_line(&mut self.buffer).await?;
118
119 if bytes_read == 0 {
120 Ok(None) } else {
122 Ok(Some(self.buffer.trim().to_string()))
123 }
124 }
125
126 async fn write_line(&mut self, line: &str) -> std::io::Result<()> {
127 self.writer.write_all(line.as_bytes()).await?;
128 self.writer.write_all(b"\n").await?;
129 Ok(())
130 }
131
132 async fn flush(&mut self) -> std::io::Result<()> {
133 self.writer.flush().await
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[tokio::test]
142 async fn test_mock_io_bidirectional() {
143 let (mut server_io, mut client_io) = MockIo::create_pair(1024);
144
145 client_io.write_line("Hello from client").await.unwrap();
147 client_io.flush().await.unwrap();
148
149 let line = server_io.read_line().await.unwrap();
150 assert_eq!(line, Some("Hello from client".to_string()));
151
152 server_io.write_line("Hello from server").await.unwrap();
154 server_io.flush().await.unwrap();
155
156 let line = client_io.read_line().await.unwrap();
157 assert_eq!(line, Some("Hello from server".to_string()));
158 }
159
160 #[tokio::test]
161 async fn test_mock_io_multiple_lines() {
162 let (mut server_io, mut client_io) = MockIo::create_pair(1024);
163
164 client_io.write_line("line1").await.unwrap();
166 client_io.write_line("line2").await.unwrap();
167 client_io.write_line("line3").await.unwrap();
168 client_io.flush().await.unwrap();
169
170 assert_eq!(
172 server_io.read_line().await.unwrap(),
173 Some("line1".to_string())
174 );
175 assert_eq!(
176 server_io.read_line().await.unwrap(),
177 Some("line2".to_string())
178 );
179 assert_eq!(
180 server_io.read_line().await.unwrap(),
181 Some("line3".to_string())
182 );
183 }
184
185 #[tokio::test]
186 async fn test_mock_io_empty_lines() {
187 let (mut server_io, mut client_io) = MockIo::create_pair(1024);
188
189 client_io.write_line("").await.unwrap();
191 client_io.flush().await.unwrap();
192
193 let line = server_io.read_line().await.unwrap();
194 assert_eq!(line, Some("".to_string()));
195 }
196
197 #[tokio::test]
198 async fn test_mock_io_eof() {
199 let (mut server_io, client_io) = MockIo::create_pair(1024);
200
201 drop(client_io);
203
204 let line = server_io.read_line().await.unwrap();
206 assert_eq!(line, None);
207 }
208
209 #[test]
214 fn test_stdio_new() {
215 let _stdio = StdIo::new();
217 }
220
221 #[test]
222 fn test_stdio_default() {
223 let _stdio = StdIo::default();
225 }
226
227 #[test]
228 fn test_stdio_clone_safety() {
229 let stdio = StdIo::new();
231 assert_eq!(stdio.buffer.len(), 0);
232 }
233
234 #[tokio::test]
239 async fn test_mock_io_whitespace_handling() {
240 let (mut server_io, mut client_io) = MockIo::create_pair(1024);
241
242 client_io.write_line(" leading spaces").await.unwrap();
244 client_io.write_line("trailing spaces ").await.unwrap();
245 client_io.write_line("\ttabs\t").await.unwrap();
246 client_io.flush().await.unwrap();
247
248 assert_eq!(
250 server_io.read_line().await.unwrap(),
251 Some("leading spaces".to_string())
252 );
253 assert_eq!(
254 server_io.read_line().await.unwrap(),
255 Some("trailing spaces".to_string())
256 );
257 assert_eq!(
258 server_io.read_line().await.unwrap(),
259 Some("tabs".to_string())
260 );
261 }
262
263 #[tokio::test]
264 async fn test_mock_io_large_messages() {
265 let (mut server_io, mut client_io) = MockIo::create_pair(8192);
266
267 let large_msg = "x".repeat(4096);
269 client_io.write_line(&large_msg).await.unwrap();
270 client_io.flush().await.unwrap();
271
272 let received = server_io.read_line().await.unwrap();
273 assert_eq!(received, Some(large_msg));
274 }
275
276 #[tokio::test]
277 async fn test_mock_io_buffer_reuse() {
278 let (mut server_io, mut client_io) = MockIo::create_pair(1024);
279
280 for i in 0..5 {
282 let msg = format!("message{}", i);
283 client_io.write_line(&msg).await.unwrap();
284 client_io.flush().await.unwrap();
285
286 let received = server_io.read_line().await.unwrap();
287 assert_eq!(received, Some(msg));
288 }
289 }
290
291 #[tokio::test]
292 async fn test_mock_io_concurrent_operations() {
293 let (mut server_io, mut client_io) = MockIo::create_pair(4096);
294
295 let client_handle = tokio::spawn(async move {
297 for i in 0..10 {
298 client_io.write_line(&format!("msg{}", i)).await.unwrap();
299 client_io.flush().await.unwrap();
300 }
301 });
302
303 for i in 0..10 {
305 let received = server_io.read_line().await.unwrap();
306 assert_eq!(received, Some(format!("msg{}", i)));
307 }
308
309 client_handle.await.unwrap();
310 }
311}