1- use std:: path:: PathBuf ;
2-
31use 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- }
352
36- type TriggerType = String ;
3+ mod environment_definition;
4+ mod loader;
375
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 } ;
979
9810pub async fn validate_application_against_environment_ids (
9911 env_ids : impl Iterator < Item = & str > ,
@@ -202,157 +114,22 @@ async fn validate_component_against_environments(
202114 Ok ( ( ) )
203115}
204116
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-
325117async fn validate_component_against_worlds (
326118 target_worlds : impl Iterator < Item = ( & str , & TargetWorld ) > ,
327119 component : & ComponentToValidate < ' _ > ,
328120 resolution_context : & ResolutionContext ,
329121) -> 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 ?;
347124
348125 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 ( ) )
350127 . await ?;
351128 }
352129
353130 tracing:: info!(
354131 "Validated component {} {} against all target worlds" ,
355- component. id,
132+ component. id( ) ,
356133 component. source_description( )
357134 ) ;
358135 Ok ( ( ) )
@@ -368,14 +145,14 @@ async fn validate_wasm_against_any_world(
368145 for target_str in target_world. versioned_names ( ) {
369146 tracing:: info!(
370147 "Trying component {} {} against target world {target_str}" ,
371- component. id,
148+ component. id( ) ,
372149 component. source_description( ) ,
373150 ) ;
374151 match validate_wasm_against_world ( env_name, & target_str, component, wasm) . await {
375152 Ok ( ( ) ) => {
376153 tracing:: info!(
377154 "Validated component {} {} against target world {target_str}" ,
378- component. id,
155+ component. id( ) ,
379156 component. source_description( ) ,
380157 ) ;
381158 return Ok ( ( ) ) ;
@@ -384,7 +161,7 @@ async fn validate_wasm_against_any_world(
384161 // Record the error, but continue in case a different world succeeds
385162 tracing:: info!(
386163 "Rejecting component {} {} for target world {target_str} because {e:?}" ,
387- component. id,
164+ component. id( ) ,
388165 component. source_description( ) ,
389166 ) ;
390167 result = Err ( e) ;
@@ -429,17 +206,17 @@ async fn validate_wasm_against_world(
429206 Ok ( _) => Ok ( ( ) ) ,
430207 Err ( wac_parser:: resolution:: Error :: TargetMismatch { kind, name, world, .. } ) => {
431208 // 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( ) ) )
433210 }
434211 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( ) ) )
436213 }
437214 Err ( wac_parser:: resolution:: Error :: PackageMissingExport { export, .. } ) => {
438215 // 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( ) ) )
440217 }
441218 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( ) ) )
443220 }
444221 Err ( e) => {
445222 Err ( anyhow ! ( e) )
0 commit comments