wasmcloud_provider_sdk/
lib.rs

1use ::core::future::Future;
2use ::core::time::Duration;
3
4use std::collections::HashMap;
5
6use anyhow::Context as _;
7use async_nats::{ConnectOptions, Event};
8use provider::ProviderInitState;
9use tracing::{error, info, warn};
10use wasmcloud_core::secrets::SecretValue;
11
12pub mod error;
13pub mod provider;
14
15#[cfg(feature = "otel")]
16pub mod otel;
17
18pub use anyhow;
19pub use provider::{
20    get_connection, load_host_data, run_provider, serve_provider_exports, ProviderConnection,
21};
22pub use tracing_subscriber;
23pub use wasmcloud_core as core;
24/// Re-export of types from [`wasmcloud_core`]
25pub use wasmcloud_core::{
26    HealthCheckRequest, HealthCheckResponse, HostData, InterfaceLinkDefinition, WitFunction,
27    WitInterface, WitNamespace, WitPackage,
28};
29pub use wasmcloud_tracing;
30
31/// Parse an sufficiently specified WIT operation/method into constituent parts.
32///
33///
34/// # Errors
35///
36/// Returns `Err` if the operation is not of the form "<package>:<ns>/<interface>.<function>"
37///
38/// # Example
39///
40/// ```no_test
41/// let (wit_ns, wit_pkg, wit_iface, wit_fn) = parse_wit_meta_from_operation(("wasmcloud:bus/guest-config"));
42/// #assert_eq!(wit_ns, "wasmcloud")
43/// #assert_eq!(wit_pkg, "bus")
44/// #assert_eq!(wit_iface, "iface")
45/// #assert_eq!(wit_fn, None)
46/// let (wit_ns, wit_pkg, wit_iface, wit_fn) = parse_wit_meta_from_operation(("wasmcloud:bus/guest-config.get"));
47/// #assert_eq!(wit_ns, "wasmcloud")
48/// #assert_eq!(wit_pkg, "bus")
49/// #assert_eq!(wit_iface, "iface")
50/// #assert_eq!(wit_fn, Some("get"))
51/// ```
52pub fn parse_wit_meta_from_operation(
53    operation: impl AsRef<str>,
54) -> anyhow::Result<(WitNamespace, WitPackage, WitInterface, Option<WitFunction>)> {
55    let operation = operation.as_ref();
56    let (ns_and_pkg, interface_and_func) = operation
57        .rsplit_once('/')
58        .context("failed to parse operation")?;
59    let (wit_iface, wit_fn) = interface_and_func
60        .split_once('.')
61        .context("interface and function should be specified")?;
62    let (wit_ns, wit_pkg) = ns_and_pkg
63        .rsplit_once(':')
64        .context("failed to parse operation for WIT ns/pkg")?;
65    Ok((
66        wit_ns.into(),
67        wit_pkg.into(),
68        wit_iface.into(),
69        if wit_fn.is_empty() {
70            None
71        } else {
72            Some(wit_fn.into())
73        },
74    ))
75}
76
77pub const URL_SCHEME: &str = "wasmbus";
78/// nats address to use if not included in initial `HostData`
79pub(crate) const DEFAULT_NATS_ADDR: &str = "nats://127.0.0.1:4222";
80/// The default timeout for a request to the lattice, in milliseconds
81pub const DEFAULT_RPC_TIMEOUT_MILLIS: Duration = Duration::from_millis(2000);
82
83/// helper method to add logging to a nats connection. Logs disconnection (warn level), reconnection (info level), error (error), slow consumer, and lame duck(warn) events.
84#[must_use]
85pub fn with_connection_event_logging(opts: ConnectOptions) -> ConnectOptions {
86    opts.event_callback(|event| async move {
87        match event {
88            Event::Connected => info!("nats client connected"),
89            Event::Disconnected => warn!("nats client disconnected"),
90            Event::Draining => warn!("nats client draining"),
91            Event::LameDuckMode => warn!("nats lame duck mode"),
92            Event::SlowConsumer(val) => warn!("nats slow consumer detected ({val})"),
93            Event::ClientError(err) => error!("nats client error: '{err:?}'"),
94            Event::ServerError(err) => error!("nats server error: '{err:?}'"),
95            Event::Closed => error!("nats client closed"),
96        }
97    })
98}
99
100/// Context - message passing metadata used by wasmCloud Capability Providers
101#[derive(Default, Debug, Clone)]
102pub struct Context {
103    /// Messages received by a Provider will have component set to the component's ID
104    pub component: Option<String>,
105
106    /// A map of tracing context information
107    pub tracing: HashMap<String, String>,
108}
109
110impl Context {
111    /// Get link name from the request.
112    ///
113    /// While link name should in theory *always* be present, it is not natively included in [`Context`] yet,
114    /// so we must retrieve it from headers on the request.
115    ///
116    /// Note that in certain (older) versions of wasmCloud it is possible for the link name to be missing
117    /// though incredibly unlikely (basically, due to a bug). In the event that the link name was *not*
118    /// properly stored on the context 'default' (the default link name) is returned as the link name.
119    #[must_use]
120    pub fn link_name(&self) -> &str {
121        self.tracing
122            .get("link-name")
123            .map_or("default", String::as_str)
124    }
125}
126
127/// Configuration of a link that is passed to a provider
128#[non_exhaustive]
129pub struct LinkConfig<'a> {
130    /// Given that the link was established with the source as this provider,
131    /// this is the target ID which should be a component
132    pub target_id: &'a str,
133
134    /// Given that the link was established with the target as this provider,
135    /// this is the source ID which should be a component
136    pub source_id: &'a str,
137
138    /// Name of the link that was provided
139    pub link_name: &'a str,
140
141    /// Configuration provided to the provider (either as the target or the source)
142    pub config: &'a HashMap<String, String>,
143
144    /// Secrets provided to the provider (either as the target or the source)
145    pub secrets: &'a HashMap<String, SecretValue>,
146
147    /// WIT metadata for the link
148    pub wit_metadata: (&'a WitNamespace, &'a WitPackage, &'a Vec<WitInterface>),
149}
150
151/// Configuration object is made available when a provider is started, to assist in init
152///
153/// This trait exists to both obscure the underlying implementation and control what information
154/// is made available
155pub trait ProviderInitConfig: Send + Sync {
156    /// Get host-configured provider ID.
157    ///
158    /// This value may not be knowable to the provider at build time but must be known by runtime.
159    fn get_provider_id(&self) -> &str;
160
161    /// Retrieve the configuration for the provider available at initialization time.
162    ///
163    /// This normally consists of named configuration that were set for the provider,
164    /// merged, and received from the host *before* the provider has started initialization.
165    fn get_config(&self) -> &HashMap<String, String>;
166
167    /// Retrieve the secrets for the provider available at initialization time.
168    ///
169    /// The return value is a map of secret names to their values and should be treated as
170    /// sensitive information, avoiding logging.
171    fn get_secrets(&self) -> &HashMap<String, SecretValue>;
172}
173
174impl ProviderInitConfig for &ProviderInitState {
175    fn get_provider_id(&self) -> &str {
176        &self.provider_key
177    }
178
179    fn get_config(&self) -> &HashMap<String, String> {
180        &self.config
181    }
182
183    fn get_secrets(&self) -> &HashMap<String, SecretValue> {
184        &self.secrets
185    }
186}
187
188/// Objects that can act as provider configuration updates
189pub trait ProviderConfigUpdate: Send + Sync {
190    /// Get the configuration values associated with the configuration update
191    fn get_values(&self) -> &HashMap<String, String>;
192}
193
194impl ProviderConfigUpdate for &HashMap<String, String> {
195    fn get_values(&self) -> &HashMap<String, String> {
196        self
197    }
198}
199
200/// Present information related to a link delete, normally used as part of the [`Provider`] interface,
201/// for providers that must process a link deletion in some way.
202pub trait LinkDeleteInfo: Send + Sync {
203    /// Retrieve the source of the link
204    ///
205    /// If the provider receiving this LinkDeleteInfo is the target, then this is
206    /// the workload that was invoking the provider (most often a component)
207    ///
208    /// If the provider receiving this LinkDeleteInfo is the source, then this is
209    /// the ID of the provider itself.
210    fn get_source_id(&self) -> &str;
211
212    /// Retrieve the target of the link
213    ///
214    /// If the provider receiving this LinkDeleteInfo is the target, then this is the ID of the provider itself.
215    ///
216    /// If the provider receiving this LinkDeleteInfo is the source (ex. a HTTP server provider which
217    /// must invoke other components/providers), then the target in this case is the thing *being invoked*,
218    /// likely a component.
219    fn get_target_id(&self) -> &str;
220
221    /// Retrieve the link name
222    fn get_link_name(&self) -> &str;
223}
224
225impl LinkDeleteInfo for &InterfaceLinkDefinition {
226    fn get_source_id(&self) -> &str {
227        &self.source_id
228    }
229
230    fn get_target_id(&self) -> &str {
231        &self.target
232    }
233
234    fn get_link_name(&self) -> &str {
235        &self.name
236    }
237}
238
239/// Capability Provider handling of messages from host
240pub trait Provider<E = anyhow::Error>: Sync {
241    /// Initialize the provider
242    ///
243    /// # Arguments
244    ///
245    /// * `static_config` - Merged named configuration attached to the provider *prior* to startup
246    fn init(
247        &self,
248        init_config: impl ProviderInitConfig,
249    ) -> impl Future<Output = Result<(), E>> + Send {
250        let _ = init_config;
251        async { Ok(()) }
252    }
253
254    /// Process a configuration update for the provider
255    ///
256    /// Providers are configured with zero or more config names which the
257    /// host combines into a single config that they are provided with.
258    ///
259    /// As named configurations change over time, the host makes updates to the
260    /// bundles of configuration that are relevant to this provider, and this method
261    /// helps the provider handle those changes.
262    ///
263    /// For more information on *how* these updates are delivered, see `run_provider()`
264    ///
265    /// # Arguments
266    ///
267    /// * `update` - The relevant configuration update
268    fn on_config_update(
269        &self,
270        update: impl ProviderConfigUpdate,
271    ) -> impl Future<Output = Result<(), E>> + Send {
272        let _ = update;
273        async { Ok(()) }
274    }
275
276    /// Receive and handle a link that has been established on the lattice where this provider is the source.
277    ///
278    /// Implement this when your provider needs to call other components.
279    ///
280    /// [Links](https://wasmcloud.com/docs/concepts/runtime-linking) are uni-directional -- a "source"
281    /// operates as one end of the link, linking to a "target". When a link is created on the lattice, and
282    /// this provider is the source, this method is called.
283    fn receive_link_config_as_source(
284        &self,
285        config: LinkConfig<'_>,
286    ) -> impl Future<Output = Result<(), E>> + Send {
287        let _ = config;
288        async { Ok(()) }
289    }
290
291    /// Receive and handle a link that has been established on the lattice where this provider is the target.
292    ///
293    /// Implement this when your provider is called by other components.
294    ///
295    /// [Links](https://wasmcloud.com/docs/concepts/runtime-linking) are uni-directional -- a "source"
296    /// operates as one end of the link, linking to a "target". When a link is created on the lattice, and
297    /// this provider is the target, this method is called.
298    fn receive_link_config_as_target(
299        &self,
300        config: LinkConfig<'_>,
301    ) -> impl Future<Output = Result<(), E>> + Send {
302        let _ = config;
303        async { Ok(()) }
304    }
305
306    /// Notify the provider that the link is dropped where the provider is the target
307    fn delete_link_as_target(
308        &self,
309        _info: impl LinkDeleteInfo,
310    ) -> impl Future<Output = Result<(), E>> + Send {
311        async { Ok(()) }
312    }
313
314    /// Notify the provider that the link is dropped where the provider is the source
315    fn delete_link_as_source(
316        &self,
317        _info: impl LinkDeleteInfo,
318    ) -> impl Future<Output = Result<(), E>> + Send {
319        async { Ok(()) }
320    }
321
322    /// Perform health check. Called at regular intervals by host
323    /// Default implementation always returns healthy
324    fn health_request(
325        &self,
326        _arg: &HealthCheckRequest,
327    ) -> impl Future<Output = Result<HealthCheckResponse, E>> + Send {
328        async {
329            Ok(HealthCheckResponse {
330                healthy: true,
331                message: None,
332            })
333        }
334    }
335
336    /// Handle system shutdown message
337    fn shutdown(&self) -> impl Future<Output = Result<(), E>> + Send {
338        async { Ok(()) }
339    }
340}