uni_plugin/host_services.rs
1//! Host service traits for capability-gated plugin host functions.
2//!
3//! `uni.kms.*` and `uni.http.*` host functions need a backing host service to
4//! perform real work. These traits define that seam in the shared `uni-plugin`
5//! crate so every loader (Rhai today; Extism / WASM at the host-fn cutover)
6//! binds the *same* abstraction rather than each inventing its own. The host
7//! supplies concrete implementations (e.g. a `reqwest`-backed [`HttpEgress`] in
8//! `uni-plugin-host`) and hands them to the loader.
9//!
10//! Secret acquisition has no trait here — it reuses
11//! [`crate::secrets::SecretStore`] directly.
12
13use std::time::Duration;
14
15use crate::errors::FnError;
16
17/// A signing / verification service backing the `uni.kms.*` host functions.
18///
19/// Implementations are expected to enforce nothing about *which* key ids are
20/// permissible — that attenuation is checked against the plugin's granted
21/// [`crate::Capability::Kms`] before this trait is called.
22pub trait KmsProvider: Send + Sync {
23 /// Sign `data` with the key identified by `key_id`, returning the raw
24 /// signature bytes.
25 ///
26 /// # Errors
27 ///
28 /// Returns [`FnError`] if the key is unknown or the signing operation
29 /// fails.
30 fn sign(&self, key_id: &str, data: &[u8]) -> Result<Vec<u8>, FnError>;
31
32 /// Verify `signature` over `data` against the key identified by `key_id`.
33 ///
34 /// # Errors
35 ///
36 /// Returns [`FnError`] if the key is unknown or verification cannot be
37 /// performed (a *valid* result of "signature does not match" is `Ok(false)`,
38 /// not an error).
39 fn verify(&self, key_id: &str, data: &[u8], signature: &[u8]) -> Result<bool, FnError>;
40}
41
42/// Response returned by an [`HttpEgress`] request.
43#[derive(Clone, Debug)]
44pub struct HttpResponse {
45 /// HTTP status code.
46 pub status: u16,
47 /// Response body, truncated to the caller's `max_bytes` limit.
48 pub body: Vec<u8>,
49}
50
51/// A **blocking** HTTP egress service backing the `uni.http.*` host functions.
52///
53/// Methods are synchronous because the Rhai engine runs scripts synchronously
54/// (inside DataFusion scalar/procedure execution). Implementations must be safe
55/// to call from within a Tokio runtime context — e.g. by running the request on
56/// a dedicated OS thread rather than blocking a Tokio worker. URL allow-listing,
57/// timeout, and response-size limits are enforced by the caller against the
58/// plugin's granted [`crate::Capability::Network`]; the `timeout` and
59/// `max_bytes` arguments carry those decisions into the request.
60///
61/// `traceparent`, when `Some`, is injected as the W3C `traceparent` request
62/// header so the host's trace context propagates across the plugin boundary
63/// into the outbound call (see [`crate::observability::TraceContext::to_traceparent`]).
64pub trait HttpEgress: Send + Sync {
65 /// Perform a blocking HTTP GET, reading at most `max_bytes` of the body.
66 ///
67 /// # Errors
68 ///
69 /// Returns [`FnError`] on connection, timeout, or transport failure.
70 fn get(
71 &self,
72 url: &str,
73 timeout: Duration,
74 max_bytes: usize,
75 traceparent: Option<&str>,
76 ) -> Result<HttpResponse, FnError>;
77
78 /// Perform a blocking HTTP POST of `body`, reading at most `max_bytes` of
79 /// the response body.
80 ///
81 /// # Errors
82 ///
83 /// Returns [`FnError`] on connection, timeout, or transport failure.
84 fn post(
85 &self,
86 url: &str,
87 body: &[u8],
88 timeout: Duration,
89 max_bytes: usize,
90 traceparent: Option<&str>,
91 ) -> Result<HttpResponse, FnError>;
92}