1use super::RunnableConfig;
8use async_trait::async_trait;
9use futures_util::Stream;
10use std::pin::Pin;
11
12#[async_trait]
39pub trait Runnable<Input: Send + Sync + 'static, Output: Send + Sync + 'static>:
40 Send + Sync
41{
42 type Error: std::error::Error + Send + Sync + 'static;
44
45 async fn invoke(
56 &self,
57 input: Input,
58 config: Option<RunnableConfig>,
59 ) -> Result<Output, Self::Error>;
60
61 async fn batch(
76 &self,
77 inputs: Vec<Input>,
78 config: Option<RunnableConfig>,
79 ) -> Result<Vec<Output>, Self::Error> {
80 use futures_util::StreamExt;
81
82 let limit = config
85 .as_ref()
86 .and_then(|c| c.max_concurrency)
87 .unwrap_or(inputs.len())
88 .max(1);
89
90 let results = futures_util::stream::iter(inputs)
91 .map(|input| {
92 let config = config.clone();
93 async move { self.invoke(input, config).await }
94 })
95 .buffered(limit)
96 .collect::<Vec<Result<_, _>>>()
97 .await;
98
99 results.into_iter().collect()
101 }
102
103 async fn batch_as_completed(
125 &self,
126 inputs: Vec<Input>,
127 config: Option<RunnableConfig>,
128 ) -> Result<Vec<(usize, Output)>, Self::Error> {
129 use futures_util::StreamExt;
130
131 let limit = config
132 .as_ref()
133 .and_then(|c| c.max_concurrency)
134 .unwrap_or(inputs.len())
135 .max(1);
136
137 let results = futures_util::stream::iter(inputs.into_iter().enumerate())
138 .map(|(index, input)| {
139 let config = config.clone();
140 async move {
141 self.invoke(input, config)
142 .await
143 .map(|output| (index, output))
144 }
145 })
146 .buffer_unordered(limit)
147 .collect::<Vec<Result<(usize, Output), _>>>()
148 .await;
149
150 results.into_iter().collect()
152 }
153
154 async fn stream(
170 &self,
171 input: Input,
172 config: Option<RunnableConfig>,
173 ) -> Result<Pin<Box<dyn Stream<Item = Result<Output, Self::Error>> + Send>>, Self::Error> {
174 let result = self.invoke(input, config).await?;
178 let stream = futures_util::stream::once(async move { Ok(result) });
179 Ok(Box::pin(stream))
180 }
181
182 async fn transform(
206 &self,
207 input: Pin<Box<dyn Stream<Item = Result<Input, Self::Error>> + Send>>,
208 config: Option<RunnableConfig>,
209 ) -> Result<Pin<Box<dyn Stream<Item = Result<Output, Self::Error>> + Send + '_>>, Self::Error>
210 {
211 use futures_util::StreamExt;
216
217 let config = config.clone();
218 let stream = async_stream::stream! {
219 let mut input = input;
220 loop {
221 let item = match input.next().await {
222 Some(Ok(item)) => item,
223 Some(Err(e)) => {
224 yield Err(e);
225 return;
226 }
227 None => return,
228 };
229 let inner = match self.stream(item, config.clone()).await {
232 Ok(s) => s,
233 Err(e) => {
234 yield Err(e);
235 return;
236 }
237 };
238 futures_util::pin_mut!(inner);
239 while let Some(res) = inner.next().await {
240 yield res;
241 }
242 }
243 };
244 Ok(Box::pin(stream))
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use futures_util::StreamExt;
252
253 struct TestRunnable;
254
255 #[async_trait]
256 impl Runnable<String, String> for TestRunnable {
257 type Error = std::convert::Infallible;
258
259 async fn invoke(
260 &self,
261 input: String,
262 _config: Option<RunnableConfig>,
263 ) -> Result<String, Self::Error> {
264 Ok(format!("processed: {}", input))
265 }
266 }
267
268 #[tokio::test]
269 async fn test_default_stream_returns_single_element() {
270 let runnable = TestRunnable;
271 let mut stream = runnable.stream("test".to_string(), None).await.unwrap();
272
273 let first = stream.next().await;
274 assert!(first.is_some());
275 assert_eq!(first.unwrap().unwrap(), "processed: test");
276
277 let second = stream.next().await;
278 assert!(second.is_none());
279 }
280
281 #[tokio::test]
282 async fn test_invoke_matches_stream_result() {
283 let runnable = TestRunnable;
284
285 let invoke_result = runnable.invoke("hello".to_string(), None).await.unwrap();
286 let mut stream = runnable.stream("hello".to_string(), None).await.unwrap();
287 let stream_result = stream.next().await.unwrap().unwrap();
288
289 assert_eq!(invoke_result, stream_result);
290 }
291
292 #[tokio::test]
293 async fn test_default_transform_maps_elementwise() {
294 let runnable = TestRunnable;
295 let input_stream = Box::pin(futures_util::stream::iter(vec![
296 Ok("first".to_string()),
297 Ok("second".to_string()),
298 Ok("third".to_string()),
299 ]))
300 as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
301
302 let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
303
304 let mut results = Vec::new();
306 while let Some(item) = output_stream.next().await {
307 results.push(item.unwrap());
308 }
309 assert_eq!(
310 results,
311 vec![
312 "processed: first".to_string(),
313 "processed: second".to_string(),
314 "processed: third".to_string(),
315 ]
316 );
317 }
318
319 #[tokio::test]
320 async fn test_default_transform_empty_input() {
321 let runnable = TestRunnable;
322 let input_stream = Box::pin(futures_util::stream::empty::<
323 Result<String, std::convert::Infallible>,
324 >())
325 as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
326
327 let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
328
329 assert!(output_stream.next().await.is_none());
331 }
332
333 #[tokio::test]
334 async fn test_default_batch_preserves_order() {
335 let runnable = TestRunnable;
336 let results = runnable
337 .batch(
338 vec!["a".to_string(), "b".to_string(), "c".to_string()],
339 None,
340 )
341 .await
342 .unwrap();
343 assert_eq!(
344 results,
345 vec![
346 "processed: a".to_string(),
347 "processed: b".to_string(),
348 "processed: c".to_string(),
349 ]
350 );
351 }
352
353 #[tokio::test]
354 async fn test_default_batch_respects_max_concurrency() {
355 let runnable = TestRunnable;
356 let config = RunnableConfig::new().with_max_concurrency(1);
357 let results = runnable
358 .batch(
359 vec!["x".to_string(), "y".to_string(), "z".to_string()],
360 Some(config),
361 )
362 .await
363 .unwrap();
364 assert_eq!(
365 results,
366 vec![
367 "processed: x".to_string(),
368 "processed: y".to_string(),
369 "processed: z".to_string(),
370 ]
371 );
372 }
373
374 #[tokio::test]
375 async fn test_default_batch_empty_input() {
376 let runnable = TestRunnable;
377 let results = runnable.batch(vec![], None).await.unwrap();
378 assert!(results.is_empty());
379 }
380
381 struct Delayed;
383
384 #[async_trait]
385 impl Runnable<&'static str, usize> for Delayed {
386 type Error = std::convert::Infallible;
387
388 async fn invoke(
389 &self,
390 input: &'static str,
391 _config: Option<RunnableConfig>,
392 ) -> Result<usize, Self::Error> {
393 match input {
394 "slow" => {
395 tokio::time::sleep(std::time::Duration::from_millis(40)).await;
396 Ok(10)
397 }
398 _ => {
399 tokio::time::sleep(std::time::Duration::from_millis(1)).await;
400 Ok(1)
401 }
402 }
403 }
404 }
405
406 #[tokio::test]
407 async fn test_batch_as_completed_returns_completion_order() {
408 let results = Delayed
409 .batch_as_completed(vec!["slow", "fast"], None)
410 .await
411 .unwrap();
412 assert_eq!(results.len(), 2);
414 assert_eq!(results[0].0, 1, "完成最快的应带原始下标 1");
415 assert_eq!(results[0].1, 1);
416 assert_eq!(results[1].0, 0);
417 assert_eq!(results[1].1, 10);
418 }
419
420 #[tokio::test]
421 async fn test_batch_as_completed_respects_max_concurrency() {
422 let config = RunnableConfig::new().with_max_concurrency(1);
423 let results = Delayed
424 .batch_as_completed(vec!["fast", "slow"], Some(config))
425 .await
426 .unwrap();
427 assert_eq!(results, vec![(0, 1), (1, 10)]);
429 }
430
431 #[tokio::test]
432 async fn test_batch_as_completed_empty_input() {
433 let runnable = TestRunnable;
434 let results = runnable.batch_as_completed(vec![], None).await.unwrap();
435 assert!(results.is_empty());
436 }
437
438 #[tokio::test]
441 async fn default_transform_is_lazy_incremental() {
442 use std::sync::atomic::{AtomicBool, Ordering};
443 use std::sync::Arc;
444
445 let produced_last = Arc::new(AtomicBool::new(false));
446 let flag = Arc::clone(&produced_last);
447
448 let src = async_stream::stream! {
450 yield Ok("first".to_string());
451 yield Ok("second".to_string());
452 yield Ok("third".to_string());
453 flag.store(true, Ordering::SeqCst);
454 };
455 let input_stream = Box::pin(src)
456 as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
457
458 let runnable = TestRunnable;
459 let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
460
461 let first = output_stream.next().await.unwrap().unwrap();
463 assert_eq!(first, "processed: first");
464 assert!(
465 !produced_last.load(Ordering::SeqCst),
466 "transform 不应在上游流结束前就攒齐整条输入"
467 );
468
469 while output_stream.next().await.is_some() {}
471 assert!(produced_last.load(Ordering::SeqCst));
472 }
473}