gsm_core/
runner_client.rs1use 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#[async_trait]
15pub trait RunnerClient: Send + Sync {
16 async fn invoke_adapter(&self, out: &OutMessage, adapter: &AdapterDescriptor) -> Result<()>;
17}
18
19#[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
38pub fn shared_client<C: RunnerClient + 'static>(client: C) -> Arc<C> {
40 Arc::new(client)
41}
42
43#[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}