1use alloc::boxed::Box;
10use derive_more::Debug;
11use tokio::io::AsyncRead;
12
13pub type AnyInput = Input;
15pub type GraphInput = Input;
18pub type NoInput = ();
20pub type QueryInput = Input;
22pub type TextInput = Input;
24
25#[derive(Debug)]
38pub enum Input {
39 Ignored,
41 AsyncRead(#[debug(skip)] Box<dyn AsyncRead + Send + Sync + Unpin>),
46 #[cfg(feature = "std")]
55 Jsonl(#[debug(skip)] crate::JsonlStream),
56}
57
58impl Input {
59 #[cfg(feature = "std")]
65 pub fn as_stdio(&self) -> std::process::Stdio {
66 use std::process::Stdio;
67 match self {
68 Input::Ignored => Stdio::null(),
69 Input::AsyncRead(_) => Stdio::piped(),
70 Input::Jsonl(_) => Stdio::piped(),
71 }
72 }
73
74 #[cfg(feature = "std")]
81 pub fn into_jsonl(self) -> Self {
82 self.into_jsonl_with_batching(crate::BatchOptions::default())
83 }
84
85 #[cfg(feature = "std")]
89 pub fn into_jsonl_with_batching(self, options: crate::BatchOptions) -> Self {
90 match self {
91 Self::AsyncRead(reader) => Self::Jsonl(crate::jsonl_batches(reader, options)),
92 input => input,
93 }
94 }
95
96 #[cfg(feature = "std")]
97 pub(crate) async fn write_to(
98 &mut self,
99 stdin: Option<tokio::process::ChildStdin>,
100 ) -> Result<(), crate::completion::InputFailure> {
101 use crate::completion::InputFailure;
102 use tokio::io::{AsyncReadExt, AsyncWriteExt};
103
104 if matches!(self, Self::Ignored) {
105 return Ok(());
106 }
107 let mut stdin = stdin.ok_or_else(|| {
108 std::io::Error::new(std::io::ErrorKind::InvalidInput, "stdin must be piped")
109 })?;
110 match self {
111 Self::Ignored => {},
112 Self::AsyncRead(reader) => {
113 let mut buffer = [0; 8192];
114 loop {
115 let count = reader
116 .read(&mut buffer)
117 .await
118 .map_err(|error| InputFailure::Source(error.into()))?;
119 if count == 0 {
120 break;
121 }
122 stdin.write_all(&buffer[..count]).await?;
123 }
124 },
125 Self::Jsonl(batches) => {
126 write_batches(batches, &mut stdin).await?;
127 },
128 }
129 stdin.shutdown().await?;
130 Ok(())
131 }
132}
133
134#[cfg(feature = "std")]
135async fn write_batches(
136 batches: &mut crate::JsonlStream,
137 writer: &mut (impl tokio::io::AsyncWrite + Unpin),
138) -> Result<(), crate::completion::InputFailure> {
139 use crate::StreamExt;
140 use crate::completion::InputFailure;
141 use tokio::io::AsyncWriteExt;
142
143 let mut buffer = alloc::vec::Vec::new();
144 while let Some(batch) = batches.next().await {
145 let batch = batch.map_err(InputFailure::Source)?;
146 if batch.is_empty() {
147 tokio::task::yield_now().await;
148 continue;
149 }
150 if let Some(bytes) = batch.as_contiguous_bytes() {
151 writer.write_all(bytes).await?;
152 continue;
153 }
154 if writer.is_write_vectored() {
155 if let Some(mut slices) = batch.wire_slices(16) {
159 let mut remaining = slices.as_mut_slice();
160 while !remaining.is_empty() {
161 let written = writer.write_vectored(remaining).await?;
162 if written == 0 {
163 return Err(std::io::Error::new(
164 std::io::ErrorKind::WriteZero,
165 "failed to write JSONL batch",
166 )
167 .into());
168 }
169 std::io::IoSlice::advance_slices(&mut remaining, written);
170 }
171 continue;
172 }
173 }
174 buffer.clear();
175 for line in batch.lines() {
176 buffer.extend_from_slice(line);
177 if !line.ends_with(b"\n") {
178 buffer.push(b'\n');
179 }
180 }
181 writer.write_all(&buffer).await?;
184 }
185 Ok(())
186}
187
188#[cfg(all(test, feature = "std"))]
189mod tests {
190 use super::*;
191 use crate::{ExecutorError, JsonlBatch, JsonlStream, completion::InputFailure};
192 use alloc::{vec, vec::Vec};
193 use core::{
194 pin::Pin,
195 task::{Context, Poll},
196 };
197 use std::io;
198 use tokio::io::AsyncWrite;
199
200 struct Destination {
201 bytes: Vec<u8>,
202 writes: usize,
203 max_write: usize,
204 vectored: bool,
205 vectored_calls: usize,
206 }
207 impl AsyncWrite for Destination {
208 fn poll_write(
209 mut self: Pin<&mut Self>,
210 _: &mut Context<'_>,
211 bytes: &[u8],
212 ) -> Poll<io::Result<usize>> {
213 let count = bytes.len().min(self.max_write);
214 self.bytes.extend_from_slice(&bytes[..count]);
215 self.writes += 1;
216 Poll::Ready(Ok(count))
217 }
218 fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
219 panic!("batches must not flush per line");
220 }
221 fn is_write_vectored(&self) -> bool {
222 self.vectored
223 }
224 fn poll_write_vectored(
225 mut self: Pin<&mut Self>,
226 _: &mut Context<'_>,
227 slices: &[io::IoSlice<'_>],
228 ) -> Poll<io::Result<usize>> {
229 self.vectored_calls += 1;
230 let mut written = 0;
231 for slice in slices {
232 let count = slice.len().min(self.max_write - written);
233 self.bytes.extend_from_slice(&slice[..count]);
234 written += count;
235 if written == self.max_write {
236 break;
237 }
238 }
239 Poll::Ready(Ok(written))
240 }
241 fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
242 Poll::Ready(Ok(()))
243 }
244 }
245
246 #[tokio::test]
247 async fn coalesces_batches_preserving_line_endings_and_handling_partial_writes() {
248 for max_write in [usize::MAX, 3] {
249 let mut batches: JsonlStream = Box::pin(crate::stream::iter([
250 Ok(JsonlBatch::default()),
251 Ok(
252 JsonlBatch::try_from(vec![b"{}".to_vec(), b"[]\r\n".to_vec(), Vec::new()])
253 .unwrap(),
254 ),
255 Ok(JsonlBatch::try_from(vec![b"last".to_vec()]).unwrap()),
256 ]));
257 let mut destination = Destination {
258 bytes: Vec::new(),
259 writes: 0,
260 max_write,
261 vectored: false,
262 vectored_calls: 0,
263 };
264 assert!(write_batches(&mut batches, &mut destination).await.is_ok());
265 assert_eq!(destination.bytes, b"{}\n[]\r\n\nlast\n");
266 if max_write == usize::MAX {
267 assert_eq!(destination.writes, 2);
268 }
269 }
270 }
271
272 #[tokio::test]
273 async fn batch_source_error_stops_writing_after_complete_batches() {
274 let mut batches: JsonlStream = Box::pin(crate::stream::iter([
275 Ok(JsonlBatch::try_from(vec![b"first".to_vec()]).unwrap()),
276 Err(ExecutorError::UnexpectedOther(io::Error::other(
277 "source failed",
278 ))),
279 Ok(JsonlBatch::try_from(vec![b"must not be written".to_vec()]).unwrap()),
280 ]));
281 let mut destination = Destination {
282 bytes: Vec::new(),
283 writes: 0,
284 max_write: usize::MAX,
285 vectored: false,
286 vectored_calls: 0,
287 };
288 assert!(matches!(
289 write_batches(&mut batches, &mut destination).await,
290 Err(InputFailure::Source(_))
291 ));
292 assert_eq!(destination.bytes, b"first\n");
293 }
294
295 #[tokio::test]
296 async fn vectored_writes_handle_partial_progress_and_insert_missing_lf() {
297 let mut batches: JsonlStream =
298 Box::pin(crate::stream::iter([Ok(JsonlBatch::try_from(vec![
299 b"ab\n".to_vec(),
300 Vec::new(),
301 b"cd".to_vec(),
302 ])
303 .unwrap())]));
304 let mut destination = Destination {
305 bytes: Vec::new(),
306 writes: 0,
307 max_write: 2,
308 vectored: true,
309 vectored_calls: 0,
310 };
311 assert!(write_batches(&mut batches, &mut destination).await.is_ok());
312 assert_eq!(destination.bytes, b"ab\n\ncd\n");
313 assert_eq!(destination.writes, 0);
314 assert!(destination.vectored_calls > 1);
315 }
316
317 #[tokio::test]
318 async fn contiguous_batches_and_fragmented_fallback_use_single_writes() {
319 use crate::Bytes;
320 let backing = Bytes::from_static(b"a\nb\n");
321 let shared = JsonlBatch::from_bytes(backing);
322 let fragmented = JsonlBatch::try_from(vec![b"x\n".to_vec(); 32]).unwrap();
323 let mut batches: JsonlStream = Box::pin(crate::stream::iter([Ok(shared), Ok(fragmented)]));
324 let mut destination = Destination {
325 bytes: Vec::new(),
326 writes: 0,
327 max_write: usize::MAX,
328 vectored: true,
329 vectored_calls: 0,
330 };
331 assert!(write_batches(&mut batches, &mut destination).await.is_ok());
332 assert_eq!(
333 destination.bytes,
334 [b"a\nb\n".to_vec(), b"x\n".repeat(32)].concat()
335 );
336 assert_eq!(destination.writes, 2);
337 assert_eq!(destination.vectored_calls, 0);
338 }
339
340 #[tokio::test]
341 async fn zero_vectored_progress_is_an_error() {
342 let mut batches: JsonlStream =
343 Box::pin(crate::stream::iter([Ok(JsonlBatch::try_from(vec![
344 b"a".to_vec(),
345 b"b".to_vec(),
346 ])
347 .unwrap())]));
348 let mut destination = Destination {
349 bytes: Vec::new(),
350 writes: 0,
351 max_write: 0,
352 vectored: true,
353 vectored_calls: 0,
354 };
355 assert!(
356 matches!(write_batches(&mut batches, &mut destination).await,
357 Err(InputFailure::Write(error)) if error.kind() == io::ErrorKind::WriteZero)
358 );
359 }
360}
361
362#[cfg(feature = "std")]
363impl Into<std::process::Stdio> for Input {
364 fn into(self) -> std::process::Stdio {
365 use std::process::Stdio;
366 match self {
367 Input::Ignored => Stdio::null(),
368 Input::AsyncRead(_) => Stdio::piped(),
369 Input::Jsonl(_) => Stdio::piped(),
370 }
371 }
372}