use super::config::RunnableConfig;
use super::error::LcelError;
use super::runnable_trait::Runnable;
use async_trait::async_trait;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
type AsyncFn<I, O> =
Arc<dyn Fn(I) -> Pin<Box<dyn Future<Output = Result<O, LcelError>> + Send>> + Send + Sync>;
pub struct RunnableLambda<I: Send + Sync + 'static, O: Send + Sync + 'static> {
func: AsyncFn<I, O>,
}
impl<I: Send + Sync + 'static, O: Send + Sync + 'static> std::fmt::Debug for RunnableLambda<I, O> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RunnableLambda")
.field("input", &std::any::type_name::<I>())
.field("output", &std::any::type_name::<O>())
.finish()
}
}
impl<I: Send + Sync + 'static, O: Send + Sync + 'static> RunnableLambda<I, O> {
pub fn new_sync<F>(func: F) -> Self
where
F: Fn(I) -> O + Send + Sync + 'static,
{
let func = Arc::new(move |input: I| {
let result = func(input);
Box::pin(async move { Ok(result) })
as Pin<Box<dyn Future<Output = Result<O, LcelError>> + Send>>
});
Self { func }
}
pub fn new_sync_fallible<F>(func: F) -> Self
where
F: Fn(I) -> Result<O, LcelError> + Send + Sync + 'static,
{
let func = Arc::new(move |input: I| {
let result = func(input);
Box::pin(async move { result })
as Pin<Box<dyn Future<Output = Result<O, LcelError>> + Send>>
});
Self { func }
}
pub fn new_async<F, Fut>(func: F) -> Self
where
F: Fn(I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<O, LcelError>> + Send + 'static,
{
let func = Arc::new(move |input: I| {
let fut = func(input);
Box::pin(fut) as Pin<Box<dyn Future<Output = Result<O, LcelError>> + Send>>
});
Self { func }
}
}
#[async_trait]
impl<I: Send + Sync + 'static, O: Send + Sync + 'static> Runnable<I, O> for RunnableLambda<I, O> {
type Error = LcelError;
async fn invoke(&self, input: I, _config: Option<RunnableConfig>) -> Result<O, LcelError> {
(self.func)(input).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::StreamExt;
#[tokio::test]
async fn sync_closure_works() {
let lambda = RunnableLambda::new_sync(|x: i32| x * 3);
let result = lambda.invoke(7, None).await.unwrap();
assert_eq!(result, 21);
}
#[tokio::test]
async fn sync_fallible_closure_ok() {
let lambda = RunnableLambda::new_sync_fallible(|x: i32| {
if x > 0 {
Ok(x * 2)
} else {
Err(LcelError::Other("must be positive".to_string()))
}
});
assert_eq!(lambda.invoke(5, None).await.unwrap(), 10);
}
#[tokio::test]
async fn sync_fallible_closure_err() {
let lambda = RunnableLambda::new_sync_fallible(|x: i32| {
if x > 0 {
Ok(x * 2)
} else {
Err(LcelError::Other("must be positive".to_string()))
}
});
let result = lambda.invoke(-1, None).await;
assert!(result.is_err());
}
#[tokio::test]
async fn async_closure_works() {
let lambda = RunnableLambda::new_async(|x: i32| async move {
tokio::task::spawn_blocking(move || x + 100)
.await
.map_err(|e| LcelError::Other(e.to_string()))
});
let result = lambda.invoke(5, None).await.unwrap();
assert_eq!(result, 105);
}
#[tokio::test]
async fn stream_uses_default() {
let lambda = RunnableLambda::new_sync(|x: i32| x + 1);
let mut stream = lambda.stream(9, None).await.unwrap();
let result = stream.next().await.unwrap().unwrap();
assert_eq!(result, 10);
assert!(stream.next().await.is_none());
}
#[tokio::test]
async fn batch_uses_default() {
let lambda = RunnableLambda::new_sync(|x: i32| x * 10);
let results = lambda.batch(vec![1, 2, 3], None).await.unwrap();
assert_eq!(results, vec![10, 20, 30]);
}
}