greentic-runner-host 0.5.21

Host runtime shim for Greentic runner: config, pack loading, activity handling
Documentation
use super::super::error::GResult;
use super::super::registry::{Adapter, AdapterCall};
use async_trait::async_trait;
use serde_json::Value;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;

type AdapterFuture = dyn Future<Output = GResult<Value>> + Send;
type AdapterInvoker = dyn Fn(AdapterCall) -> Pin<Box<AdapterFuture>> + Send + Sync;

#[async_trait]
pub trait AdapterBridge: Send + Sync {
    async fn invoke(&self, call: AdapterCall) -> GResult<Value>;
}

#[async_trait]
impl<T> Adapter for T
where
    T: AdapterBridge,
{
    async fn call(&self, call: &AdapterCall) -> GResult<Value> {
        self.invoke(call.clone()).await
    }
}

pub struct FnAdapterBridge {
    inner: Arc<AdapterInvoker>,
}

impl FnAdapterBridge {
    pub fn new<F, Fut>(func: F) -> Self
    where
        F: Send + Sync + 'static + Fn(AdapterCall) -> Fut,
        Fut: Future<Output = GResult<Value>> + Send + 'static,
    {
        let invoker: Arc<AdapterInvoker> = Arc::new(move |call: AdapterCall| {
            let fut = func(call);
            Box::pin(fut) as Pin<Box<AdapterFuture>>
        });
        Self { inner: invoker }
    }
}

#[async_trait]
impl AdapterBridge for FnAdapterBridge {
    async fn invoke(&self, call: AdapterCall) -> GResult<Value> {
        (self.inner)(call).await
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[tokio::test]
    async fn fn_adapter_bridge_invokes_closure() {
        let bridge = FnAdapterBridge::new(|call: AdapterCall| async move {
            Ok(json!({
                "adapter": call.adapter,
                "operation": call.operation,
                "payload": call.payload,
            }))
        });
        let call = AdapterCall {
            adapter: "legacy".into(),
            operation: "run".into(),
            payload: json!({"ok": true}),
        };

        let result = bridge.invoke(call).await.expect("invoke");
        assert_eq!(result["adapter"], "legacy");
        assert_eq!(result["operation"], "run");
        assert_eq!(result["payload"]["ok"], true);
    }

    #[tokio::test]
    async fn adapter_trait_forwards_to_bridge() {
        let bridge =
            FnAdapterBridge::new(
                |call: AdapterCall| async move { Ok(json!({ "echo": call.payload })) },
            );
        let call = AdapterCall {
            adapter: "legacy".into(),
            operation: "echo".into(),
            payload: json!({"value": 7}),
        };

        let result = Adapter::call(&bridge, &call).await.expect("adapter call");
        assert_eq!(result["echo"]["value"], 7);
    }
}