1
+ //! Spin internal application interfaces
2
+ //!
3
+ //! This crate contains interfaces to Spin application configuration to be used
4
+ //! by crates that implement Spin execution environments: trigger executors and
5
+ //! host components, in particular.
6
+
7
+ #![ deny( missing_docs) ]
8
+
1
9
mod host_component;
2
10
pub mod locked;
3
11
pub mod values;
@@ -13,37 +21,53 @@ pub use async_trait::async_trait;
13
21
pub use host_component:: DynamicHostComponent ;
14
22
pub use locked:: Variable ;
15
23
24
+ /// A trait for implementing the low-level operations needed to load an [`App`].
16
25
// TODO(lann): Should this migrate to spin-loader?
17
26
#[ async_trait]
18
27
pub trait Loader {
28
+ /// Called with an implementation-defined `uri` pointing to some
29
+ /// representation of a [`LockedApp`], which will be loaded.
19
30
async fn load_app ( & self , uri : & str ) -> anyhow:: Result < LockedApp > ;
20
31
32
+ /// Called with a [`LockedComponentSource`] pointing to a Wasm module
33
+ /// binary, which will be loaded.
21
34
async fn load_module (
22
35
& self ,
23
36
engine : & wasmtime:: Engine ,
24
37
source : & LockedComponentSource ,
25
38
) -> anyhow:: Result < spin_core:: Module > ;
26
39
40
+ /// Called with an [`AppComponent`]; any `files` configured with the
41
+ /// component should be "mounted" into the `store_builder`, via e.g.
42
+ /// [`StoreBuilder::read_only_preopened_dir`].
27
43
async fn mount_files (
28
44
& self ,
29
45
store_builder : & mut StoreBuilder ,
30
46
component : & AppComponent ,
31
47
) -> anyhow:: Result < ( ) > ;
32
48
}
33
49
50
+ /// An `AppLoader` holds an implementation of [`Loader`] along with
51
+ /// [`DynamicHostComponents`] configuration.
34
52
pub struct AppLoader {
35
53
inner : Box < dyn Loader + Send + Sync > ,
36
54
dynamic_host_components : DynamicHostComponents ,
37
55
}
38
56
39
57
impl AppLoader {
58
+ /// Creates a new [`AppLoader`].
40
59
pub fn new ( loader : impl Loader + Send + Sync + ' static ) -> Self {
41
60
Self {
42
61
inner : Box :: new ( loader) ,
43
62
dynamic_host_components : Default :: default ( ) ,
44
63
}
45
64
}
46
65
66
+ /// Adds a [`DynamicHostComponent`] to the given [`EngineBuilder`] and
67
+ /// configures this [`AppLoader`] to update it on component instantiation.
68
+ ///
69
+ /// This calls [`EngineBuilder::add_host_component`] for you; it should not
70
+ /// be called separately.
47
71
pub fn add_dynamic_host_component < T : Send + Sync , DHC : DynamicHostComponent > (
48
72
& mut self ,
49
73
engine_builder : & mut EngineBuilder < T > ,
@@ -53,6 +77,7 @@ impl AppLoader {
53
77
. add_dynamic_host_component ( engine_builder, host_component)
54
78
}
55
79
80
+ /// Loads an [`App`] from the given `Loader`-implementation-specific `uri`.
56
81
pub async fn load_app ( & self , uri : String ) -> Result < App > {
57
82
let locked = self
58
83
. inner
@@ -66,6 +91,8 @@ impl AppLoader {
66
91
} )
67
92
}
68
93
94
+ /// Loads an [`OwnedApp`] from the given `Loader`-implementation-specific
95
+ /// `uri`; the [`OwnedApp`] takes ownership of this [`AppLoader`].
69
96
pub async fn load_owned_app ( self , uri : String ) -> Result < OwnedApp > {
70
97
OwnedApp :: try_new_async ( self , |loader| Box :: pin ( loader. load_app ( uri) ) ) . await
71
98
}
@@ -88,11 +115,13 @@ pub struct OwnedApp {
88
115
}
89
116
90
117
impl OwnedApp {
118
+ /// Returns a reference to the owned [`App`].
91
119
pub fn borrowed ( & self ) -> & App {
92
120
self . borrow_app ( )
93
121
}
94
122
}
95
123
124
+ /// An `App` holds loaded configuration for a Spin application.
96
125
#[ derive( Debug ) ]
97
126
pub struct App < ' a > {
98
127
loader : & ' a AppLoader ,
@@ -101,10 +130,16 @@ pub struct App<'a> {
101
130
}
102
131
103
132
impl < ' a > App < ' a > {
133
+ /// Returns a [`Loader`]-implementation-specific URI for this app.
104
134
pub fn uri ( & self ) -> & str {
105
135
& self . uri
106
136
}
107
137
138
+ /// Deserializes typed metadata for this app.
139
+ ///
140
+ /// Returns `Ok(None)` if there is no metadata for the given `key` and an
141
+ /// `Err` only if there _is_ a value for the `key` but the typed
142
+ /// deserialization failed.
108
143
pub fn get_metadata < ' this , T : Deserialize < ' this > > ( & ' this self , key : & str ) -> Result < Option < T > > {
109
144
self . locked
110
145
. metadata
@@ -113,76 +148,100 @@ impl<'a> App<'a> {
113
148
. transpose ( )
114
149
}
115
150
151
+ /// Deserializes typed metadata for this app.
152
+ ///
153
+ /// Like [`App::get_metadata`], but returns an `Err` if there is no metadata
154
+ /// for the given `key`.
116
155
pub fn require_metadata < ' this , T : Deserialize < ' this > > ( & ' this self , key : & str ) -> Result < T > {
117
156
self . get_metadata ( key) ?
118
- . ok_or_else ( || Error :: ManifestError ( format ! ( "missing required {key:?}" ) ) )
157
+ . ok_or_else ( || Error :: MetadataError ( format ! ( "missing required {key:?}" ) ) )
119
158
}
120
159
160
+ /// Returns an iterator of custom config [`Variable`]s defined for this app.
121
161
pub fn variables ( & self ) -> impl Iterator < Item = ( & String , & Variable ) > {
122
162
self . locked . variables . iter ( )
123
163
}
124
164
165
+ /// Returns an iterator of [`AppComponent`]s defined for this app.
125
166
pub fn components ( & self ) -> impl Iterator < Item = AppComponent > {
126
167
self . locked
127
168
. components
128
169
. iter ( )
129
170
. map ( |locked| AppComponent { app : self , locked } )
130
171
}
131
172
173
+ /// Returns the [`AppComponent`] with the given `component_id`, or `None`
174
+ /// if it doesn't exist.
132
175
pub fn get_component ( & self , component_id : & str ) -> Option < AppComponent > {
133
176
self . components ( )
134
177
. find ( |component| component. locked . id == component_id)
135
178
}
136
179
180
+ /// Returns an iterator of [`AppTrigger`]s defined for this app.
137
181
pub fn triggers ( & self ) -> impl Iterator < Item = AppTrigger > {
138
182
self . locked
139
183
. triggers
140
184
. iter ( )
141
185
. map ( |locked| AppTrigger { app : self , locked } )
142
186
}
143
187
188
+ /// Returns an iterator of [`AppTrigger`]s defined for this app with
189
+ /// the given `trigger_type`.
144
190
pub fn triggers_with_type ( & ' a self , trigger_type : & ' a str ) -> impl Iterator < Item = AppTrigger > {
145
191
self . triggers ( )
146
192
. filter ( move |trigger| trigger. locked . trigger_type == trigger_type)
147
193
}
148
194
}
149
195
196
+ /// An `AppComponent` holds configuration for a Spin application component.
150
197
pub struct AppComponent < ' a > {
198
+ /// The app this component belongs to.
151
199
pub app : & ' a App < ' a > ,
152
200
locked : & ' a LockedComponent ,
153
201
}
154
202
155
203
impl < ' a > AppComponent < ' a > {
204
+ /// Returns this component's app-unique ID.
156
205
pub fn id ( & self ) -> & str {
157
206
& self . locked . id
158
207
}
159
208
209
+ /// Returns this component's Wasm module source.
160
210
pub fn source ( & self ) -> & LockedComponentSource {
161
211
& self . locked . source
162
212
}
163
213
214
+ /// Returns an iterator of [`ContentPath`]s for this component's configured
215
+ /// "directory mounts".
164
216
pub fn files ( & self ) -> std:: slice:: Iter < ContentPath > {
165
217
self . locked . files . iter ( )
166
218
}
167
219
220
+ /// Deserializes typed metadata for this component.
221
+ ///
222
+ /// Returns `Ok(None)` if there is no metadata for the given `key` and an
223
+ /// `Err` only if there _is_ a value for the `key` but the typed
224
+ /// deserialization failed.
168
225
pub fn get_metadata < T : Deserialize < ' a > > ( & self , key : & str ) -> Result < Option < T > > {
169
226
self . locked
170
227
. metadata
171
228
. get ( key)
172
229
. map ( |value| {
173
230
T :: deserialize ( value) . map_err ( |err| {
174
- Error :: ManifestError ( format ! (
231
+ Error :: MetadataError ( format ! (
175
232
"failed to deserialize {key:?} = {value:?}: {err:?}"
176
233
) )
177
234
} )
178
235
} )
179
236
. transpose ( )
180
237
}
181
238
239
+ /// Returns an iterator of custom config values for this component.
182
240
pub fn config ( & self ) -> impl Iterator < Item = ( & String , & String ) > {
183
241
self . locked . config . iter ( )
184
242
}
185
243
244
+ /// Loads and returns the [`spin_core::Module`] for this component.
186
245
pub async fn load_module < T : Send + Sync > (
187
246
& self ,
188
247
engine : & Engine < T > ,
@@ -195,6 +254,11 @@ impl<'a> AppComponent<'a> {
195
254
. map_err ( Error :: LoaderError )
196
255
}
197
256
257
+ /// Updates the given [`StoreBuilder`] with configuration for this component.
258
+ ///
259
+ /// In particular, the WASI 'env' and "preloaded dirs" are set up, and any
260
+ /// [`DynamicHostComponent`]s associated with the source [`AppLoader`] are
261
+ /// configured.
198
262
pub async fn apply_store_config ( & self , builder : & mut StoreBuilder ) -> Result < ( ) > {
199
263
builder. env ( & self . locked . env ) . map_err ( Error :: CoreError ) ?;
200
264
@@ -214,58 +278,74 @@ impl<'a> AppComponent<'a> {
214
278
}
215
279
}
216
280
281
+ /// An `AppTrigger` holds configuration for a Spin application trigger.
217
282
pub struct AppTrigger < ' a > {
283
+ /// The app this trigger belongs to.
218
284
pub app : & ' a App < ' a > ,
219
285
locked : & ' a LockedTrigger ,
220
286
}
221
287
222
288
impl < ' a > AppTrigger < ' a > {
289
+ /// Returns this trigger's app-unique ID.
223
290
pub fn id ( & self ) -> & str {
224
291
& self . locked . id
225
292
}
226
293
294
+ /// Returns the Trigger's type.
227
295
pub fn trigger_type ( & self ) -> & str {
228
296
& self . locked . trigger_type
229
297
}
230
298
299
+ /// Returns a reference to the [`AppComponent`] configured for this trigger.
300
+ ///
301
+ /// This is a convenience wrapper that looks up the component based on the
302
+ /// 'component' metadata value which is conventionally a component ID.
231
303
pub fn component ( & self ) -> Result < AppComponent < ' a > > {
232
304
let component_id = self . locked . trigger_config . get ( "component" ) . ok_or_else ( || {
233
- Error :: ManifestError ( format ! (
305
+ Error :: MetadataError ( format ! (
234
306
"trigger {:?} missing 'component' config field" ,
235
307
self . locked. id
236
308
) )
237
309
} ) ?;
238
310
let component_id = component_id. as_str ( ) . ok_or_else ( || {
239
- Error :: ManifestError ( format ! (
311
+ Error :: MetadataError ( format ! (
240
312
"trigger {:?} 'component' field has unexpected value {:?}" ,
241
313
self . locked. id, component_id
242
314
) )
243
315
} ) ?;
244
316
self . app . get_component ( component_id) . ok_or_else ( || {
245
- Error :: ManifestError ( format ! (
317
+ Error :: MetadataError ( format ! (
246
318
"missing component {:?} configured for trigger {:?}" ,
247
319
component_id, self . locked. id
248
320
) )
249
321
} )
250
322
}
251
323
324
+ /// Deserializes this trigger's configuration into a typed value.
252
325
pub fn typed_config < Config : Deserialize < ' a > > ( & self ) -> Result < Config > {
253
326
Ok ( Config :: deserialize ( & self . locked . trigger_config ) ?)
254
327
}
255
328
}
256
329
330
+ /// Type alias for a [`Result`]s with [`Error`].
257
331
pub type Result < T > = std:: result:: Result < T , Error > ;
258
332
333
+ /// Errors returned by methods in this crate.
259
334
#[ derive( Debug , thiserror:: Error ) ]
260
335
pub enum Error {
336
+ /// An error propagated from the [`spin_core`] crate.
261
337
#[ error( "spin core error: {0:#}" ) ]
262
338
CoreError ( anyhow:: Error ) ,
339
+ /// An error from a [`DynamicHostComponent`].
263
340
#[ error( "host component error: {0:#}" ) ]
264
341
HostComponentError ( anyhow:: Error ) ,
342
+ /// An error from a [`Loader`] implementation.
265
343
#[ error( "loader error: {0:#}" ) ]
266
344
LoaderError ( anyhow:: Error ) ,
267
- #[ error( "manifest error: {0}" ) ]
268
- ManifestError ( String ) ,
345
+ /// An error indicating missing or unexpected metadata.
346
+ #[ error( "metadata error: {0}" ) ]
347
+ MetadataError ( String ) ,
348
+ /// An error indicating failed JSON (de)serialization.
269
349
#[ error( "json error: {0}" ) ]
270
350
JsonError ( #[ from] serde_json:: Error ) ,
271
351
}
0 commit comments