wasmcloud_provider_sdk/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
use ::core::future::Future;
use ::core::time::Duration;

use std::collections::HashMap;

use anyhow::Context as _;
use async_nats::{ConnectOptions, Event};
use provider::ProviderInitState;
use tracing::{error, info, warn};
use wasmcloud_core::secrets::SecretValue;

pub mod error;
pub mod provider;

#[cfg(feature = "otel")]
pub mod otel;

pub use anyhow;
pub use provider::{
    get_connection, load_host_data, run_provider, serve_provider_exports, ProviderConnection,
};
pub use tracing_subscriber;
pub use wasmcloud_core as core;
/// Re-export of types from [`wasmcloud_core`]
pub use wasmcloud_core::{
    HealthCheckRequest, HealthCheckResponse, HostData, InterfaceLinkDefinition, WitFunction,
    WitInterface, WitNamespace, WitPackage,
};
pub use wasmcloud_tracing;

/// Parse an sufficiently specified WIT operation/method into constituent parts.
///
///
/// # Errors
///
/// Returns `Err` if the operation is not of the form "<package>:<ns>/<interface>.<function>"
///
/// # Example
///
/// ```no_test
/// let (wit_ns, wit_pkg, wit_iface, wit_fn) = parse_wit_meta_from_operation(("wasmcloud:bus/guest-config"));
/// #assert_eq!(wit_ns, "wasmcloud")
/// #assert_eq!(wit_pkg, "bus")
/// #assert_eq!(wit_iface, "iface")
/// #assert_eq!(wit_fn, None)
/// let (wit_ns, wit_pkg, wit_iface, wit_fn) = parse_wit_meta_from_operation(("wasmcloud:bus/guest-config.get"));
/// #assert_eq!(wit_ns, "wasmcloud")
/// #assert_eq!(wit_pkg, "bus")
/// #assert_eq!(wit_iface, "iface")
/// #assert_eq!(wit_fn, Some("get"))
/// ```
pub fn parse_wit_meta_from_operation(
    operation: impl AsRef<str>,
) -> anyhow::Result<(WitNamespace, WitPackage, WitInterface, Option<WitFunction>)> {
    let operation = operation.as_ref();
    let (ns_and_pkg, interface_and_func) = operation
        .rsplit_once('/')
        .context("failed to parse operation")?;
    let (wit_iface, wit_fn) = interface_and_func
        .split_once('.')
        .context("interface and function should be specified")?;
    let (wit_ns, wit_pkg) = ns_and_pkg
        .rsplit_once(':')
        .context("failed to parse operation for WIT ns/pkg")?;
    Ok((
        wit_ns.into(),
        wit_pkg.into(),
        wit_iface.into(),
        if wit_fn.is_empty() {
            None
        } else {
            Some(wit_fn.into())
        },
    ))
}

pub const URL_SCHEME: &str = "wasmbus";
/// nats address to use if not included in initial `HostData`
pub(crate) const DEFAULT_NATS_ADDR: &str = "nats://127.0.0.1:4222";
/// The default timeout for a request to the lattice, in milliseconds
pub const DEFAULT_RPC_TIMEOUT_MILLIS: Duration = Duration::from_millis(2000);

/// 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.
#[must_use]
pub fn with_connection_event_logging(opts: ConnectOptions) -> ConnectOptions {
    opts.event_callback(|event| async move {
        match event {
            Event::Disconnected => warn!("nats client disconnected"),
            Event::Connected => info!("nats client connected"),
            Event::ClientError(err) => error!("nats client error: '{:?}'", err),
            Event::ServerError(err) => error!("nats server error: '{:?}'", err),
            Event::SlowConsumer(val) => warn!("nats slow consumer detected ({})", val),
            Event::LameDuckMode => warn!("nats lame duck mode"),
        }
    })
}

/// Context - message passing metadata used by wasmCloud Capability Providers
#[derive(Default, Debug, Clone)]
pub struct Context {
    /// Messages received by a Provider will have component set to the component's ID
    pub component: Option<String>,

    /// A map of tracing context information
    pub tracing: HashMap<String, String>,
}

impl Context {
    /// Get link name from the request.
    ///
    /// While link name should in theory *always* be present, it is not natively included in [`Context`] yet,
    /// so we must retrieve it from headers on the request.
    ///
    /// Note that in certain (older) versions of wasmCloud it is possible for the link name to be missing
    /// though incredibly unlikely (basically, due to a bug). In the event that the link name was *not*
    /// properly stored on the context 'default' (the default link name) is returned as the link name.
    #[must_use]
    pub fn link_name(&self) -> &str {
        self.tracing
            .get("link-name")
            .map_or("default", String::as_str)
    }
}

/// Configuration of a link that is passed to a provider
#[non_exhaustive]
pub struct LinkConfig<'a> {
    /// Given that the link was established with the source as this provider,
    /// this is the target ID which should be a component
    pub target_id: &'a str,

    /// Given that the link was established with the target as this provider,
    /// this is the source ID which should be a component
    pub source_id: &'a str,

    /// Name of the link that was provided
    pub link_name: &'a str,

    /// Configuration provided to the provider (either as the target or the source)
    pub config: &'a HashMap<String, String>,

    /// Secrets provided to the provider (either as the target or the source)
    pub secrets: &'a HashMap<String, SecretValue>,

    /// WIT metadata for the link
    pub wit_metadata: (&'a WitNamespace, &'a WitPackage, &'a Vec<WitInterface>),
}

/// Configuration object is made available when a provider is started, to assist in init
///
/// This trait exists to both obscure the underlying implementation and control what information
/// is made available
pub trait ProviderInitConfig: Send + Sync {
    /// Get host-configured provider ID.
    ///
    /// This value may not be knowable to the provider at build time but must be known by runtime.
    fn get_provider_id(&self) -> &str;

    /// Retrieve the configuration for the provider available at initialization time.
    ///
    /// This normally consists of named configuration that were set for the provider,
    /// merged, and received from the host *before* the provider has started initialization.
    fn get_config(&self) -> &HashMap<String, String>;

    /// Retrieve the secrets for the provider available at initialization time.
    ///
    /// The return value is a map of secret names to their values and should be treated as
    /// sensitive information, avoiding logging.
    fn get_secrets(&self) -> &HashMap<String, SecretValue>;
}

impl ProviderInitConfig for &ProviderInitState {
    fn get_provider_id(&self) -> &str {
        &self.provider_key
    }

    fn get_config(&self) -> &HashMap<String, String> {
        &self.config
    }

    fn get_secrets(&self) -> &HashMap<String, SecretValue> {
        &self.secrets
    }
}

/// Objects that can act as provider configuration updates
pub trait ProviderConfigUpdate: Send + Sync {
    /// Get the configuration values associated with the configuration update
    fn get_values(&self) -> &HashMap<String, String>;
}

impl ProviderConfigUpdate for &HashMap<String, String> {
    fn get_values(&self) -> &HashMap<String, String> {
        self
    }
}

/// Present information related to a link delete, normally used as part of the [`Provider`] interface,
/// for providers that must process a link deletion in some way.
pub trait LinkDeleteInfo: Send + Sync {
    /// Retrieve the source of the link
    ///
    /// If the provider receiving this LinkDeleteInfo is the target, then this is
    /// the workload that was invoking the provider (most often a component)
    ///
    /// If the provider receiving this LinkDeleteInfo is the source, then this is
    /// the ID of the provider itself.
    fn get_source_id(&self) -> &str;

    /// Retrieve the target of the link
    ///
    /// If the provider receiving this LinkDeleteInfo is the target, then this is the ID of the provider itself.
    ///
    /// If the provider receiving this LinkDeleteInfo is the source (ex. a HTTP server provider which
    /// must invoke other components/providers), then the target in this case is the thing *being invoked*,
    /// likely a component.
    fn get_target_id(&self) -> &str;

    /// Retrieve the link name
    fn get_link_name(&self) -> &str;
}

impl LinkDeleteInfo for &InterfaceLinkDefinition {
    fn get_source_id(&self) -> &str {
        &self.source_id
    }

    fn get_target_id(&self) -> &str {
        &self.target
    }

    fn get_link_name(&self) -> &str {
        &self.name
    }
}

/// Capability Provider handling of messages from host
pub trait Provider<E = anyhow::Error>: Sync {
    /// Initialize the provider
    ///
    /// # Arguments
    ///
    /// * `static_config` - Merged named configuration attached to the provider *prior* to startup
    fn init(
        &self,
        init_config: impl ProviderInitConfig,
    ) -> impl Future<Output = Result<(), E>> + Send {
        let _ = init_config;
        async { Ok(()) }
    }

    /// Process a configuration update for the provider
    ///
    /// Providers are configured with zero or more config names which the
    /// host combines into a single config that they are provided with.
    ///
    /// As named configurations change over time, the host makes updates to the
    /// bundles of configuration that are relevant to this provider, and this method
    /// helps the provider handle those changes.
    ///
    /// For more information on *how* these updates are delivered, see `run_provider()`
    ///
    /// # Arguments
    ///
    /// * `update` - The relevant configuration update
    fn on_config_update(
        &self,
        update: impl ProviderConfigUpdate,
    ) -> impl Future<Output = Result<(), E>> + Send {
        let _ = update;
        async { Ok(()) }
    }

    /// Receive and handle a link that has been established on the lattice where this provider is the source.
    ///
    /// Implement this when your provider needs to call other components.
    ///
    /// [Links](https://wasmcloud.com/docs/concepts/runtime-linking) are uni-directional -- a "source"
    /// operates as one end of the link, linking to a "target". When a link is created on the lattice, and
    /// this provider is the source, this method is called.
    fn receive_link_config_as_source(
        &self,
        config: LinkConfig<'_>,
    ) -> impl Future<Output = Result<(), E>> + Send {
        let _ = config;
        async { Ok(()) }
    }

    /// Receive and handle a link that has been established on the lattice where this provider is the target.
    ///
    /// Implement this when your provider is called by other components.
    ///
    /// [Links](https://wasmcloud.com/docs/concepts/runtime-linking) are uni-directional -- a "source"
    /// operates as one end of the link, linking to a "target". When a link is created on the lattice, and
    /// this provider is the target, this method is called.
    fn receive_link_config_as_target(
        &self,
        config: LinkConfig<'_>,
    ) -> impl Future<Output = Result<(), E>> + Send {
        let _ = config;
        async { Ok(()) }
    }

    /// Notify the provider that the link is dropped where the provider is the target
    fn delete_link_as_target(
        &self,
        _info: impl LinkDeleteInfo,
    ) -> impl Future<Output = Result<(), E>> + Send {
        async { Ok(()) }
    }

    /// Notify the provider that the link is dropped where the provider is the source
    fn delete_link_as_source(
        &self,
        _info: impl LinkDeleteInfo,
    ) -> impl Future<Output = Result<(), E>> + Send {
        async { Ok(()) }
    }

    /// Perform health check. Called at regular intervals by host
    /// Default implementation always returns healthy
    fn health_request(
        &self,
        _arg: &HealthCheckRequest,
    ) -> impl Future<Output = Result<HealthCheckResponse, E>> + Send {
        async {
            Ok(HealthCheckResponse {
                healthy: true,
                message: None,
            })
        }
    }

    /// Handle system shutdown message
    fn shutdown(&self) -> impl Future<Output = Result<(), E>> + Send {
        async { Ok(()) }
    }
}