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).await.map(|output| (index, output))
142 }
143 })
144 .buffer_unordered(limit)
145 .collect::<Vec<Result<(usize, Output), _>>>()
146 .await;
147
148 results.into_iter().collect()
150 }
151
152 async fn stream(
168 &self,
169 input: Input,
170 config: Option<RunnableConfig>,
171 ) -> Result<Pin<Box<dyn Stream<Item = Result<Output, Self::Error>> + Send>>, Self::Error> {
172 let result = self.invoke(input, config).await?;
176 let stream = futures_util::stream::once(async move { Ok(result) });
177 Ok(Box::pin(stream))
178 }
179
180 async fn transform(
200 &self,
201 input: Pin<Box<dyn Stream<Item = Result<Input, Self::Error>> + Send>>,
202 config: Option<RunnableConfig>,
203 ) -> Result<Pin<Box<dyn Stream<Item = Result<Output, Self::Error>> + Send>>, Self::Error> {
204 use futures_util::StreamExt;
205
206 let mut items = Vec::new();
209 let mut input = input;
210 while let Some(item) = input.next().await {
211 items.push(item?);
212 }
213
214 let mut per_item_streams = Vec::with_capacity(items.len());
215 for item in items {
216 let stream = self.stream(item, config.clone()).await?;
217 per_item_streams.push(stream);
218 }
219
220 let flattened = futures_util::stream::iter(per_item_streams).flatten();
221 Ok(Box::pin(flattened))
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use futures_util::StreamExt;
229
230 struct TestRunnable;
231
232 #[async_trait]
233 impl Runnable<String, String> for TestRunnable {
234 type Error = std::convert::Infallible;
235
236 async fn invoke(
237 &self,
238 input: String,
239 _config: Option<RunnableConfig>,
240 ) -> Result<String, Self::Error> {
241 Ok(format!("processed: {}", input))
242 }
243 }
244
245 #[tokio::test]
246 async fn test_default_stream_returns_single_element() {
247 let runnable = TestRunnable;
248 let mut stream = runnable.stream("test".to_string(), None).await.unwrap();
249
250 let first = stream.next().await;
251 assert!(first.is_some());
252 assert_eq!(first.unwrap().unwrap(), "processed: test");
253
254 let second = stream.next().await;
255 assert!(second.is_none());
256 }
257
258 #[tokio::test]
259 async fn test_invoke_matches_stream_result() {
260 let runnable = TestRunnable;
261
262 let invoke_result = runnable.invoke("hello".to_string(), None).await.unwrap();
263 let mut stream = runnable.stream("hello".to_string(), None).await.unwrap();
264 let stream_result = stream.next().await.unwrap().unwrap();
265
266 assert_eq!(invoke_result, stream_result);
267 }
268
269 #[tokio::test]
270 async fn test_default_transform_maps_elementwise() {
271 let runnable = TestRunnable;
272 let input_stream = Box::pin(futures_util::stream::iter(vec![
273 Ok("first".to_string()),
274 Ok("second".to_string()),
275 Ok("third".to_string()),
276 ]))
277 as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
278
279 let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
280
281 let mut results = Vec::new();
283 while let Some(item) = output_stream.next().await {
284 results.push(item.unwrap());
285 }
286 assert_eq!(
287 results,
288 vec![
289 "processed: first".to_string(),
290 "processed: second".to_string(),
291 "processed: third".to_string(),
292 ]
293 );
294 }
295
296 #[tokio::test]
297 async fn test_default_transform_empty_input() {
298 let runnable = TestRunnable;
299 let input_stream = Box::pin(futures_util::stream::empty::<
300 Result<String, std::convert::Infallible>,
301 >())
302 as Pin<Box<dyn Stream<Item = Result<String, std::convert::Infallible>> + Send>>;
303
304 let mut output_stream = runnable.transform(input_stream, None).await.unwrap();
305
306 assert!(output_stream.next().await.is_none());
308 }
309
310 #[tokio::test]
311 async fn test_default_batch_preserves_order() {
312 let runnable = TestRunnable;
313 let results = runnable
314 .batch(
315 vec!["a".to_string(), "b".to_string(), "c".to_string()],
316 None,
317 )
318 .await
319 .unwrap();
320 assert_eq!(
321 results,
322 vec![
323 "processed: a".to_string(),
324 "processed: b".to_string(),
325 "processed: c".to_string(),
326 ]
327 );
328 }
329
330 #[tokio::test]
331 async fn test_default_batch_respects_max_concurrency() {
332 let runnable = TestRunnable;
333 let config = RunnableConfig::new().with_max_concurrency(1);
334 let results = runnable
335 .batch(
336 vec!["x".to_string(), "y".to_string(), "z".to_string()],
337 Some(config),
338 )
339 .await
340 .unwrap();
341 assert_eq!(
342 results,
343 vec![
344 "processed: x".to_string(),
345 "processed: y".to_string(),
346 "processed: z".to_string(),
347 ]
348 );
349 }
350
351 #[tokio::test]
352 async fn test_default_batch_empty_input() {
353 let runnable = TestRunnable;
354 let results = runnable.batch(vec![], None).await.unwrap();
355 assert!(results.is_empty());
356 }
357
358 struct Delayed;
360
361 #[async_trait]
362 impl Runnable<&'static str, usize> for Delayed {
363 type Error = std::convert::Infallible;
364
365 async fn invoke(
366 &self,
367 input: &'static str,
368 _config: Option<RunnableConfig>,
369 ) -> Result<usize, Self::Error> {
370 match input {
371 "slow" => {
372 tokio::time::sleep(std::time::Duration::from_millis(40)).await;
373 Ok(10)
374 }
375 _ => {
376 tokio::time::sleep(std::time::Duration::from_millis(1)).await;
377 Ok(1)
378 }
379 }
380 }
381 }
382
383 #[tokio::test]
384 async fn test_batch_as_completed_returns_completion_order() {
385 let results = Delayed
386 .batch_as_completed(vec!["slow", "fast"], None)
387 .await
388 .unwrap();
389 assert_eq!(results.len(), 2);
391 assert_eq!(results[0].0, 1, "完成最快的应带原始下标 1");
392 assert_eq!(results[0].1, 1);
393 assert_eq!(results[1].0, 0);
394 assert_eq!(results[1].1, 10);
395 }
396
397 #[tokio::test]
398 async fn test_batch_as_completed_respects_max_concurrency() {
399 let config = RunnableConfig::new().with_max_concurrency(1);
400 let results = Delayed
401 .batch_as_completed(vec!["fast", "slow"], Some(config))
402 .await
403 .unwrap();
404 assert_eq!(results, vec![(0, 1), (1, 10)]);
406 }
407
408 #[tokio::test]
409 async fn test_batch_as_completed_empty_input() {
410 let runnable = TestRunnable;
411 let results = runnable.batch_as_completed(vec![], None).await.unwrap();
412 assert!(results.is_empty());
413 }
414}