Skip to main content

gsm_core/
runner_client.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use reqwest::Client;
4use serde::Serialize;
5use std::sync::Arc;
6use tracing::info;
7
8use crate::{AdapterDescriptor, OutMessage};
9
10/// Abstraction for invoking adapter components via an external runner.
11///
12/// Implementations can use HTTP, NATS, or any other transport; this crate does not
13/// assume a concrete runner dependency.
14#[async_trait]
15pub trait RunnerClient: Send + Sync {
16    async fn invoke_adapter(&self, out: &OutMessage, adapter: &AdapterDescriptor) -> Result<()>;
17}
18
19/// Default stub client that only logs invocation. Useful for local/dev and tests.
20#[derive(Default)]
21pub struct LoggingRunnerClient;
22
23#[async_trait]
24impl RunnerClient for LoggingRunnerClient {
25    async fn invoke_adapter(&self, out: &OutMessage, adapter: &AdapterDescriptor) -> Result<()> {
26        info!(
27            tenant = %out.tenant,
28            platform = %out.platform.as_str(),
29            adapter = %adapter.name,
30            component = %adapter.component,
31            flow = ?adapter.flow_path(),
32            "RunnerClient stub invoked adapter"
33        );
34        Ok(())
35    }
36}
37
38/// Helper to wrap a shared client.
39pub fn shared_client<C: RunnerClient + 'static>(client: C) -> Arc<C> {
40    Arc::new(client)
41}
42
43/// Simple HTTP-based runner client that POSTs adapter invocations to an external runner service.
44#[derive(Clone)]
45pub struct HttpRunnerClient {
46    client: Client,
47    url: String,
48    api_key: Option<String>,
49}
50
51impl HttpRunnerClient {
52    pub fn new(url: impl Into<String>, api_key: Option<String>) -> Result<Self> {
53        Ok(Self {
54            client: Client::new(),
55            url: url.into(),
56            api_key,
57        })
58    }
59}
60
61#[derive(Serialize)]
62struct InvocationPayload<'a> {
63    adapter: &'a AdapterDescriptor,
64    message: &'a OutMessage,
65}
66
67#[async_trait]
68impl RunnerClient for HttpRunnerClient {
69    async fn invoke_adapter(&self, out: &OutMessage, adapter: &AdapterDescriptor) -> Result<()> {
70        let payload = InvocationPayload {
71            adapter,
72            message: out,
73        };
74        let mut req = self.client.post(&self.url).json(&payload);
75        if let Some(key) = &self.api_key {
76            req = req.header("Authorization", format!("Bearer {key}"));
77        }
78        let resp = req.send().await?;
79        let status = resp.status();
80        let body = resp.text().await.unwrap_or_default();
81        if !status.is_success() {
82            anyhow::bail!("runner returned {} body={}", status, body);
83        }
84        Ok(())
85    }
86}