Skip to content

Commit 2900e6e

Browse files
committed
Tidying and splitting up
Signed-off-by: itowlson <[email protected]>
1 parent d840496 commit 2900e6e

File tree

4 files changed

+234
-242
lines changed

4 files changed

+234
-242
lines changed

crates/build/src/lib.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@ pub async fn build(manifest_file: &Path, component_ids: &[String]) -> Result<()>
3838

3939
if let Ok(manifest) = &manifest {
4040
if !deployment_targets.is_empty() {
41-
let resolution_context = spin_environments::ResolutionContext {
42-
base_dir: manifest_file.parent().unwrap().to_owned(),
43-
};
41+
let resolution_context =
42+
spin_environments::ResolutionContext::new(manifest_file.parent().unwrap()).await?;
4443
spin_environments::validate_application_against_environment_ids(
4544
deployment_targets.iter(),
4645
manifest,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use wasm_pkg_loader::PackageRef;
2+
3+
#[derive(Debug, serde::Deserialize)]
4+
pub struct TargetEnvironment {
5+
pub name: String,
6+
pub environments: std::collections::HashMap<TriggerType, TargetWorld>,
7+
}
8+
9+
#[derive(Debug, Eq, Hash, PartialEq, serde::Deserialize)]
10+
pub struct TargetWorld {
11+
wit_package: PackageRef,
12+
package_ver: String, // TODO: tidy to semver::Version
13+
world_name: WorldNames,
14+
}
15+
16+
#[derive(Debug, Eq, Hash, PartialEq, serde::Deserialize)]
17+
#[serde(untagged)]
18+
enum WorldNames {
19+
Exactly(String),
20+
AnyOf(Vec<String>),
21+
}
22+
23+
impl TargetWorld {
24+
fn versioned_name(&self, world_name: &str) -> String {
25+
format!("{}/{}@{}", self.wit_package, world_name, self.package_ver)
26+
}
27+
28+
pub fn versioned_names(&self) -> Vec<String> {
29+
match &self.world_name {
30+
WorldNames::Exactly(name) => vec![self.versioned_name(name)],
31+
WorldNames::AnyOf(names) => {
32+
names.iter().map(|name| self.versioned_name(name)).collect()
33+
}
34+
}
35+
}
36+
}
37+
38+
pub type TriggerType = String;

crates/environments/src/lib.rs

+16-239
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,11 @@
1-
use std::path::PathBuf;
2-
31
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-
}
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

9810
pub 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-
325117
async 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

Comments
 (0)