1
- use std:: path:: PathBuf ;
2
-
3
1
use anyhow:: { anyhow, Context } ;
4
- use spin_common:: ui:: quoted_path;
5
- use wasm_pkg_loader:: PackageRef ;
6
-
7
- #[ derive( Debug , Eq , Hash , PartialEq , serde:: Deserialize ) ]
8
- struct TargetWorld {
9
- wit_package : PackageRef ,
10
- package_ver : String , // TODO: tidy to semver::Version
11
- world_name : WorldNames ,
12
- }
13
-
14
- #[ derive( Debug , Eq , Hash , PartialEq , serde:: Deserialize ) ]
15
- #[ serde( untagged) ]
16
- enum WorldNames {
17
- Exactly ( String ) ,
18
- AnyOf ( Vec < String > ) ,
19
- }
20
-
21
- impl TargetWorld {
22
- fn versioned_name ( & self , world_name : & str ) -> String {
23
- format ! ( "{}/{}@{}" , self . wit_package, world_name, self . package_ver)
24
- }
25
-
26
- fn versioned_names ( & self ) -> Vec < String > {
27
- match & self . world_name {
28
- WorldNames :: Exactly ( name) => vec ! [ self . versioned_name( name) ] ,
29
- WorldNames :: AnyOf ( names) => {
30
- names. iter ( ) . map ( |name| self . versioned_name ( name) ) . collect ( )
31
- }
32
- }
33
- }
34
- }
35
2
36
- type TriggerType = String ;
3
+ mod environment_definition;
4
+ mod loader;
37
5
38
- struct ComponentToValidate < ' a > {
39
- id : & ' a str ,
40
- source : & ' a spin_manifest:: schema:: v2:: ComponentSource ,
41
- dependencies : WrappedComponentDependencies ,
42
- }
43
-
44
- impl < ' a > ComponentToValidate < ' a > {
45
- fn source_description ( & self ) -> String {
46
- match self . source {
47
- spin_manifest:: schema:: v2:: ComponentSource :: Local ( path) => {
48
- format ! ( "file {}" , quoted_path( path) )
49
- }
50
- spin_manifest:: schema:: v2:: ComponentSource :: Remote { url, .. } => format ! ( "URL {url}" ) ,
51
- spin_manifest:: schema:: v2:: ComponentSource :: Registry { package, .. } => {
52
- format ! ( "package {package}" )
53
- }
54
- }
55
- }
56
- }
57
-
58
- #[ derive( Debug , serde:: Deserialize ) ]
59
- pub struct TargetEnvironment {
60
- name : String ,
61
- environments : std:: collections:: HashMap < TriggerType , TargetWorld > ,
62
- }
63
-
64
- pub struct ResolutionContext {
65
- pub base_dir : PathBuf ,
66
- }
67
-
68
- fn component_source < ' a > (
69
- app : & ' a spin_manifest:: schema:: v2:: AppManifest ,
70
- trigger : & ' a spin_manifest:: schema:: v2:: Trigger ,
71
- ) -> anyhow:: Result < ComponentToValidate < ' a > > {
72
- let component_spec = trigger
73
- . component
74
- . as_ref ( )
75
- . ok_or_else ( || anyhow ! ( "No component specified for trigger {}" , trigger. id) ) ?;
76
- let ( id, source, dependencies) = match component_spec {
77
- spin_manifest:: schema:: v2:: ComponentSpec :: Inline ( c) => {
78
- ( trigger. id . as_str ( ) , & c. source , & c. dependencies )
79
- }
80
- spin_manifest:: schema:: v2:: ComponentSpec :: Reference ( r) => {
81
- let id = r. as_ref ( ) ;
82
- let Some ( component) = app. components . get ( r) else {
83
- anyhow:: bail!(
84
- "Component {id} specified for trigger {} does not exist" ,
85
- trigger. id
86
- ) ;
87
- } ;
88
- ( id, & component. source , & component. dependencies )
89
- }
90
- } ;
91
- Ok ( ComponentToValidate {
92
- id,
93
- source,
94
- dependencies : WrappedComponentDependencies :: new ( dependencies) ,
95
- } )
96
- }
6
+ use environment_definition:: { TargetEnvironment , TargetWorld , TriggerType } ;
7
+ pub use loader:: ResolutionContext ;
8
+ use loader:: { component_source, ComponentSourceLoader , ComponentToValidate } ;
97
9
98
10
pub async fn validate_application_against_environment_ids (
99
11
env_ids : impl Iterator < Item = & str > ,
@@ -202,157 +114,22 @@ async fn validate_component_against_environments(
202
114
Ok ( ( ) )
203
115
}
204
116
205
- impl ResolutionContext {
206
- async fn wasm_loader ( & self ) -> anyhow:: Result < spin_loader:: WasmLoader > {
207
- spin_loader:: WasmLoader :: new ( self . base_dir . clone ( ) , None , None ) . await
208
- }
209
- }
210
-
211
- struct ComponentSourceLoader < ' a > {
212
- wasm_loader : spin_loader:: WasmLoader ,
213
- _phantom : std:: marker:: PhantomData < & ' a usize > ,
214
- }
215
-
216
- #[ async_trait:: async_trait]
217
- impl < ' a > spin_compose:: ComponentSourceLoader for ComponentSourceLoader < ' a > {
218
- type Component = ComponentToValidate < ' a > ;
219
- type Dependency = WrappedComponentDependency ;
220
- async fn load_component_source ( & self , source : & Self :: Component ) -> anyhow:: Result < Vec < u8 > > {
221
- use spin_compose:: ComponentLike ;
222
- let path = self
223
- . wasm_loader
224
- . load_component_source ( source. id ( ) , source. source )
225
- . await ?;
226
- let bytes = tokio:: fs:: read ( & path) . await ?;
227
- let component = spin_componentize:: componentize_if_necessary ( & bytes) ?;
228
- Ok ( component. into ( ) )
229
- }
230
-
231
- async fn load_dependency_source ( & self , source : & Self :: Dependency ) -> anyhow:: Result < Vec < u8 > > {
232
- let ( path, _) = self
233
- . wasm_loader
234
- . load_component_dependency ( & source. name , & source. dependency )
235
- . await ?;
236
- let bytes = tokio:: fs:: read ( & path) . await ?;
237
- let component = spin_componentize:: componentize_if_necessary ( & bytes) ?;
238
- Ok ( component. into ( ) )
239
- }
240
- }
241
-
242
- // This exists only to thwart the orphan rule
243
- struct WrappedComponentDependency {
244
- name : spin_serde:: DependencyName ,
245
- dependency : spin_manifest:: schema:: v2:: ComponentDependency ,
246
- }
247
-
248
- // To manage lifetimes around the thwarting of the orphan rule
249
- struct WrappedComponentDependencies {
250
- dependencies : indexmap:: IndexMap < spin_serde:: DependencyName , WrappedComponentDependency > ,
251
- }
252
-
253
- impl WrappedComponentDependencies {
254
- fn new ( deps : & spin_manifest:: schema:: v2:: ComponentDependencies ) -> Self {
255
- let dependencies = deps
256
- . inner
257
- . clone ( )
258
- . into_iter ( )
259
- . map ( |( k, v) | {
260
- (
261
- k. clone ( ) ,
262
- WrappedComponentDependency {
263
- name : k,
264
- dependency : v,
265
- } ,
266
- )
267
- } )
268
- . collect ( ) ;
269
- Self { dependencies }
270
- }
271
- }
272
-
273
- #[ async_trait:: async_trait]
274
- impl < ' a > spin_compose:: ComponentLike for ComponentToValidate < ' a > {
275
- type Dependency = WrappedComponentDependency ;
276
-
277
- fn dependencies (
278
- & self ,
279
- ) -> impl std:: iter:: ExactSizeIterator < Item = ( & spin_serde:: DependencyName , & Self :: Dependency ) >
280
- {
281
- self . dependencies . dependencies . iter ( )
282
- }
283
-
284
- fn id ( & self ) -> & str {
285
- self . id
286
- }
287
- }
288
-
289
- #[ async_trait:: async_trait]
290
- impl spin_compose:: DependencyLike for WrappedComponentDependency {
291
- // async fn load_bytes(&self) -> anyhow::Result<Vec<u8>> { todo!() }
292
- fn inherit ( & self ) -> spin_compose:: InheritConfiguration {
293
- // We don't care because this never runs - it is only used to
294
- // verify import satisfaction
295
- spin_compose:: InheritConfiguration :: All
296
- }
297
-
298
- fn export ( & self ) -> & Option < String > {
299
- match & self . dependency {
300
- spin_manifest:: schema:: v2:: ComponentDependency :: Version ( _) => & None ,
301
- spin_manifest:: schema:: v2:: ComponentDependency :: Package { export, .. } => export,
302
- spin_manifest:: schema:: v2:: ComponentDependency :: Local { export, .. } => export,
303
- }
304
- }
305
- }
306
-
307
- // #[async_trait::async_trait]
308
- // impl<'a> spin_compose::ComponentSourceLoader for CSL<'a> {
309
- // type Component = ComponentToValidate<'a>;
310
- // type Dependency = DEPPY;
311
- // async fn load_component_source(
312
- // &self,
313
- // source: &Self::Component,
314
- // ) -> anyhow::Result<Vec<u8>> {
315
- // todo!()
316
- // }
317
- // async fn load_dependency_source(
318
- // &self,
319
- // source: &Self::Dependency,
320
- // ) -> anyhow::Result<Vec<u8>> {
321
- // todo!()
322
- // }
323
- // }
324
-
325
117
async fn validate_component_against_worlds (
326
118
target_worlds : impl Iterator < Item = ( & str , & TargetWorld ) > ,
327
119
component : & ComponentToValidate < ' _ > ,
328
120
resolution_context : & ResolutionContext ,
329
121
) -> anyhow:: Result < ( ) > {
330
- // let raw_wasm = resolution_context
331
- // .load_wasm(component)
332
- // .await
333
- // .with_context(|| format!("Couldn't read Wasm {}", component.source_description()))?;
334
- let loader = ComponentSourceLoader {
335
- wasm_loader : resolution_context. wasm_loader ( ) . await ?,
336
- _phantom : std:: marker:: PhantomData ,
337
- } ;
338
- let cooked_wasm = spin_compose:: compose ( & loader, component) . await ?;
339
- // FUTURE: take in manifest composition as well
340
- // let cooked_wasm =
341
- // spin_componentize::componentize_if_necessary(&raw_wasm).with_context(|| {
342
- // format!(
343
- // "Couldn't componentize Wasm {}",
344
- // component.source_description()
345
- // )
346
- // })?;
122
+ let loader = ComponentSourceLoader :: new ( resolution_context. wasm_loader ( ) ) ;
123
+ let wasm_bytes = spin_compose:: compose ( & loader, component) . await ?;
347
124
348
125
for ( env_name, target_world) in target_worlds {
349
- validate_wasm_against_any_world ( env_name, target_world, component, cooked_wasm . as_ref ( ) )
126
+ validate_wasm_against_any_world ( env_name, target_world, component, wasm_bytes . as_ref ( ) )
350
127
. await ?;
351
128
}
352
129
353
130
tracing:: info!(
354
131
"Validated component {} {} against all target worlds" ,
355
- component. id,
132
+ component. id( ) ,
356
133
component. source_description( )
357
134
) ;
358
135
Ok ( ( ) )
@@ -368,14 +145,14 @@ async fn validate_wasm_against_any_world(
368
145
for target_str in target_world. versioned_names ( ) {
369
146
tracing:: info!(
370
147
"Trying component {} {} against target world {target_str}" ,
371
- component. id,
148
+ component. id( ) ,
372
149
component. source_description( ) ,
373
150
) ;
374
151
match validate_wasm_against_world ( env_name, & target_str, component, wasm) . await {
375
152
Ok ( ( ) ) => {
376
153
tracing:: info!(
377
154
"Validated component {} {} against target world {target_str}" ,
378
- component. id,
155
+ component. id( ) ,
379
156
component. source_description( ) ,
380
157
) ;
381
158
return Ok ( ( ) ) ;
@@ -384,7 +161,7 @@ async fn validate_wasm_against_any_world(
384
161
// Record the error, but continue in case a different world succeeds
385
162
tracing:: info!(
386
163
"Rejecting component {} {} for target world {target_str} because {e:?}" ,
387
- component. id,
164
+ component. id( ) ,
388
165
component. source_description( ) ,
389
166
) ;
390
167
result = Err ( e) ;
@@ -429,17 +206,17 @@ async fn validate_wasm_against_world(
429
206
Ok ( _) => Ok ( ( ) ) ,
430
207
Err ( wac_parser:: resolution:: Error :: TargetMismatch { kind, name, world, .. } ) => {
431
208
// This one doesn't seem to get hit at the moment - we get MissingTargetExport or ImportNotInTarget instead
432
- Err ( anyhow ! ( "Component {} ({}) can't run in environment {env_name} because world {world} expects an {} named {name}" , component. id, component. source_description( ) , kind. to_string( ) . to_lowercase( ) ) )
209
+ Err ( anyhow ! ( "Component {} ({}) can't run in environment {env_name} because world {world} expects an {} named {name}" , component. id( ) , component. source_description( ) , kind. to_string( ) . to_lowercase( ) ) )
433
210
}
434
211
Err ( wac_parser:: resolution:: Error :: MissingTargetExport { name, world, .. } ) => {
435
- Err ( anyhow ! ( "Component {} ({}) can't run in environment {env_name} because world {world} requires an export named {name}, which the component does not provide" , component. id, component. source_description( ) ) )
212
+ Err ( anyhow ! ( "Component {} ({}) can't run in environment {env_name} because world {world} requires an export named {name}, which the component does not provide" , component. id( ) , component. source_description( ) ) )
436
213
}
437
214
Err ( wac_parser:: resolution:: Error :: PackageMissingExport { export, .. } ) => {
438
215
// TODO: The export here seems wrong - it seems to contain the world name rather than the interface name
439
- Err ( anyhow ! ( "Component {} ({}) can't run in environment {env_name} because world {target_str} requires an export named {export}, which the component does not provide" , component. id, component. source_description( ) ) )
216
+ Err ( anyhow ! ( "Component {} ({}) can't run in environment {env_name} because world {target_str} requires an export named {export}, which the component does not provide" , component. id( ) , component. source_description( ) ) )
440
217
}
441
218
Err ( wac_parser:: resolution:: Error :: ImportNotInTarget { name, world, .. } ) => {
442
- Err ( anyhow ! ( "Component {} ({}) can't run in environment {env_name} because world {world} does not provide an import named {name}, which the component requires" , component. id, component. source_description( ) ) )
219
+ Err ( anyhow ! ( "Component {} ({}) can't run in environment {env_name} because world {world} does not provide an import named {name}, which the component requires" , component. id( ) , component. source_description( ) ) )
443
220
}
444
221
Err ( e) => {
445
222
Err ( anyhow ! ( e) )
0 commit comments