Skip to main content

synwire_core/runnables/
lambda.rs

1//! Lambda runnable wrapping an arbitrary async closure.
2
3use std::sync::Arc;
4
5use crate::BoxFuture;
6use crate::error::SynwireError;
7use crate::runnables::config::RunnableConfig;
8use crate::runnables::core::RunnableCore;
9use serde_json::Value;
10
11/// Type alias for the wrapped lambda function.
12type LambdaFn = Arc<dyn Fn(Value) -> BoxFuture<'static, Result<Value, SynwireError>> + Send + Sync>;
13
14/// A runnable that wraps an async closure.
15///
16/// # Example
17///
18/// ```rust,no_run
19/// # use synwire_core::runnables::{RunnableLambda, RunnableCore};
20/// # use serde_json::Value;
21/// let double = RunnableLambda::new(|v: Value| {
22///     Box::pin(async move {
23///         let n = v.as_i64().unwrap_or(0) * 2;
24///         Ok(Value::from(n))
25///     })
26/// });
27/// ```
28pub struct RunnableLambda {
29    func: LambdaFn,
30    name: String,
31}
32
33impl RunnableLambda {
34    /// Create a new lambda runnable from an async closure.
35    pub fn new<F>(func: F) -> Self
36    where
37        F: Fn(Value) -> BoxFuture<'static, Result<Value, SynwireError>> + Send + Sync + 'static,
38    {
39        Self {
40            func: Arc::new(func),
41            name: "RunnableLambda".into(),
42        }
43    }
44
45    /// Set a custom name for this lambda.
46    #[must_use]
47    pub fn with_name(mut self, name: impl Into<String>) -> Self {
48        self.name = name.into();
49        self
50    }
51}
52
53impl RunnableCore for RunnableLambda {
54    fn invoke<'a>(
55        &'a self,
56        input: Value,
57        _config: Option<&'a RunnableConfig>,
58    ) -> BoxFuture<'a, Result<Value, SynwireError>> {
59        (self.func)(input)
60    }
61
62    fn name(&self) -> &str {
63        &self.name
64    }
65}
66
67#[cfg(test)]
68#[allow(clippy::unwrap_used)]
69mod tests {
70    use super::*;
71
72    #[tokio::test]
73    async fn test_lambda_invokes() {
74        let lambda = RunnableLambda::new(|v: Value| {
75            Box::pin(async move {
76                let s = v.as_str().unwrap_or("").to_uppercase();
77                Ok(Value::from(s))
78            })
79        });
80
81        let result = lambda.invoke(Value::from("hello"), None).await.unwrap();
82        assert_eq!(result, Value::from("HELLO"));
83    }
84
85    #[tokio::test]
86    async fn test_lambda_with_name() {
87        let lambda =
88            RunnableLambda::new(|v: Value| Box::pin(async move { Ok(v) })).with_name("my_func");
89        assert_eq!(lambda.name(), "my_func");
90    }
91
92    #[tokio::test]
93    async fn test_lambda_default_name() {
94        let lambda = RunnableLambda::new(|v: Value| Box::pin(async move { Ok(v) }));
95        assert_eq!(lambda.name(), "RunnableLambda");
96    }
97}