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}