Skip to content

Commit 534433e

Browse files
fibonacci1729itowlson
authored andcommitted
Switch to using wac's programmatic api
Signed-off-by: Brian Hardock <[email protected]>
1 parent 44f10a1 commit 534433e

File tree

6 files changed

+106
-97
lines changed

6 files changed

+106
-97
lines changed

Cargo.lock

Lines changed: 1 addition & 46 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/build/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ mod tests {
296296

297297
let err = target_validation.errors()[0].to_string();
298298

299-
assert!(err.contains("world wasi:cli/[email protected] does not provide an import named"));
299+
assert!(err.contains("can't run in environment wasi-minimal"));
300+
assert!(err.contains("world wasi:cli/[email protected]"));
301+
assert!(err.contains("requires imports named"));
302+
assert!(err.contains("wasi:cli/stdout"));
300303
}
301304
}

crates/environments/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ spin-serde = { path = "../serde" }
2626
toml = { workspace = true }
2727
tokio = { version = "1.23", features = ["fs"] }
2828
tracing = { workspace = true }
29-
wac-parser = "0.8"
30-
wac-resolver = "0.8"
29+
wac-graph = "0.8"
3130
wac-types = "0.8"
3231
wasm-pkg-client = { workspace = true }
3332
wasmparser = { workspace = true }

crates/environments/src/environment.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,11 @@ impl CandidateWorld {
150150
&self.package_bytes
151151
}
152152

153+
/// Returns the world name (e.g. `command` in `wasi:cli/command`).
154+
pub fn world_name(&self) -> &str {
155+
self.world.name()
156+
}
157+
153158
fn from_package_bytes(world: &WorldName, bytes: Vec<u8>) -> anyhow::Result<Self> {
154159
let decoded = wit_component::decode(&bytes)
155160
.with_context(|| format!("Failed to decode package for environment {world}"))?;

crates/environments/src/environment/definition.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ impl WorldName {
8989
&self.package
9090
}
9191

92+
pub fn name(&self) -> &str {
93+
&self.world
94+
}
95+
9296
pub fn package_namespaced_name(&self) -> String {
9397
format!("{}:{}", self.package.namespace, self.package.name)
9498
}

crates/environments/src/lib.rs

Lines changed: 91 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -163,61 +163,61 @@ async fn validate_wasm_against_world(
163163
target_world: &CandidateWorld,
164164
component: &ComponentToValidate<'_>,
165165
) -> anyhow::Result<()> {
166-
// Because we are abusing a composition tool to do validation, we have to
167-
// provide a name by which to refer to the component in the dummy composition.
168-
let component_name = "root:component";
169-
let component_key = wac_types::BorrowedPackageKey::from_name_and_version(component_name, None);
170-
171-
// wac is going to get the world from the environment package bytes.
172-
// This constructs a key for that mapping.
173-
let env_pkg_name = target_world.package_namespaced_name();
174-
let env_pkg_key = wac_types::BorrowedPackageKey::from_name_and_version(
175-
&env_pkg_name,
176-
target_world.package_version(),
177-
);
166+
use wac_types::{validate_target, ItemKind, Package as WacPackage, Types as WacTypes, WorldId};
178167

179-
let env_name = env.name();
168+
// Gets the selected world from the component encoded WIT package
169+
// TODO: make this an export on `wac_types::Types`.
170+
fn get_wit_world(
171+
types: &WacTypes,
172+
top_level_world: WorldId,
173+
world_name: &str,
174+
) -> anyhow::Result<WorldId> {
175+
let top_level_world = &types[top_level_world];
176+
let world = top_level_world
177+
.exports
178+
.get(world_name)
179+
.with_context(|| format!("wit package did not contain a world named '{world_name}'"))?;
180180

181-
let wac_text = format!(
182-
r#"
183-
package validate:[email protected] targets {target_world};
184-
let c = new {component_name} {{ ... }};
185-
export c...;
186-
"#
187-
);
181+
let ItemKind::Type(wac_types::Type::World(world_id)) = world else {
182+
// We expect the top-level world to export a world type
183+
anyhow::bail!("wit package was not encoded properly")
184+
};
185+
let wit_world = &types[*world_id];
186+
let world = wit_world.exports.values().next();
187+
let Some(ItemKind::Component(w)) = world else {
188+
// We expect the nested world type to export a component
189+
anyhow::bail!("wit package was not encoded properly")
190+
};
191+
Ok(*w)
192+
}
193+
194+
let mut types = WacTypes::default();
195+
196+
let target_world_package = WacPackage::from_bytes(
197+
&target_world.package_namespaced_name(),
198+
target_world.package_version(),
199+
target_world.package_bytes(),
200+
&mut types,
201+
)?;
188202

189-
let doc = wac_parser::Document::parse(&wac_text)
190-
.context("Internal error constructing WAC document for target checking")?;
203+
let target_world_id =
204+
get_wit_world(&types, target_world_package.ty(), target_world.world_name())?;
191205

192-
let mut packages: indexmap::IndexMap<wac_types::BorrowedPackageKey, Vec<u8>> =
193-
Default::default();
206+
let component_package =
207+
WacPackage::from_bytes(component.id(), None, component.wasm_bytes(), &mut types)?;
194208

195-
packages.insert(env_pkg_key, target_world.package_bytes().to_vec());
196-
packages.insert(component_key, component.wasm_bytes().to_vec());
209+
let target_result = validate_target(&types, target_world_id, component_package.ty());
197210

198-
match doc.resolve(packages) {
211+
match target_result {
199212
Ok(_) => Ok(()),
200-
Err(wac_parser::resolution::Error::TargetMismatch { kind, name, world, .. }) => {
201-
// This one doesn't seem to get hit at the moment - we get MissingTargetExport or ImportNotInTarget instead
202-
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()))
203-
}
204-
Err(wac_parser::resolution::Error::MissingTargetExport { name, world, .. }) => {
205-
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()))
206-
}
207-
Err(wac_parser::resolution::Error::PackageMissingExport { export, .. }) => {
208-
// TODO: The export here seems wrong - it seems to contain the world name rather than the interface name
209-
Err(anyhow!("Component {} ({}) can't run in environment {env_name} because world {target_world} requires an export named {export}, which the component does not provide", component.id(), component.source_description()))
210-
}
211-
Err(wac_parser::resolution::Error::ImportNotInTarget { name, world, .. }) => {
212-
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()))
213-
}
214-
Err(wac_parser::resolution::Error::SpreadExportNoEffect { .. }) => {
215-
// We don't have any name info in this case, but it *may* indicate that the component doesn't provide any export at all
216-
Err(anyhow!("Component {} ({}) can't run in environment {env_name} because it requires an export which the component does not provide", component.id(), component.source_description()))
217-
}
218-
Err(e) => {
219-
Err(anyhow!(e))
220-
},
213+
Err(report) => Err(format_target_result_error(
214+
&types,
215+
env.name(),
216+
target_world.to_string(),
217+
component.id(),
218+
component.source_description(),
219+
&report,
220+
)),
221221
}
222222
}
223223

@@ -242,3 +242,46 @@ fn validate_host_reqs(
242242
fn satisfies(host_caps: &[String], host_req: &String) -> bool {
243243
host_caps.contains(host_req)
244244
}
245+
246+
fn format_target_result_error(
247+
types: &wac_types::Types,
248+
env_name: &str,
249+
target_world_name: String,
250+
component_id: &str,
251+
source_description: &str,
252+
report: &wac_types::TargetValidationReport,
253+
) -> anyhow::Error {
254+
let mut error_string = format!(
255+
"Component {} ({}) can't run in environment {} because world {} ...\n",
256+
component_id, source_description, env_name, target_world_name
257+
);
258+
259+
for (idx, import) in report.imports_not_in_target().enumerate() {
260+
if idx == 0 {
261+
error_string.push_str("... requires imports named\n - ");
262+
} else {
263+
error_string.push_str(" - ");
264+
}
265+
error_string.push_str(import);
266+
error_string.push('\n');
267+
}
268+
269+
for (idx, (export, export_kind)) in report.missing_exports().enumerate() {
270+
if idx == 0 {
271+
error_string.push_str("... requires exports named\n - ");
272+
} else {
273+
error_string.push_str(" - ");
274+
}
275+
error_string.push_str(export);
276+
error_string.push_str(" (");
277+
error_string.push_str(export_kind.desc(types));
278+
error_string.push_str(")\n");
279+
}
280+
281+
for (name, extern_kind, error) in report.mismatched_types() {
282+
error_string.push_str("... found a type mismatch for ");
283+
error_string.push_str(&format!("{extern_kind} {name}: {error}"));
284+
}
285+
286+
anyhow!(error_string)
287+
}

0 commit comments

Comments
 (0)