Skip to content

Commit 7c08322

Browse files
committed
Make targets an api expose via wac-types
Signed-off-by: Brian Hardock <brian.hardock@fermyon.com>
1 parent b63595f commit 7c08322

3 files changed

Lines changed: 161 additions & 87 deletions

File tree

crates/wac-types/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ mod checker;
77
mod component;
88
mod core;
99
mod package;
10+
mod targets;
1011

1112
pub use aggregator::*;
1213
pub use checker::*;
1314
pub use component::*;
1415
pub use core::*;
1516
pub use package::*;
17+
pub use targets::*;

crates/wac-types/src/targets.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
use crate::{ExternKind, ItemKind, SubtypeChecker, Types, WorldId};
2+
use std::collections::{BTreeMap, BTreeSet};
3+
use std::fmt;
4+
5+
/// The result of validating a component against a target world.
6+
pub type TargetValidationResult = Result<(), TargetValidationReport>;
7+
8+
/// The report of validating a component against a target world.
9+
#[derive(Debug, Default)]
10+
pub struct TargetValidationReport {
11+
/// Imports present in the component but not in the target world.
12+
imports_not_in_target: BTreeSet<String>,
13+
/// Exports not in the component but required by the target world.
14+
///
15+
/// This is a mapping from the name of the missing export to the expected item kind.
16+
missing_exports: BTreeMap<String, ItemKind>,
17+
/// Mismatched types between the component and the target world.
18+
///
19+
/// This is a mapping from name of the mismatched item to a tuple of
20+
/// the extern kind of the item and type error.
21+
mismatched_types: BTreeMap<String, (ExternKind, anyhow::Error)>,
22+
}
23+
24+
impl From<TargetValidationReport> for TargetValidationResult {
25+
fn from(report: TargetValidationReport) -> TargetValidationResult {
26+
if report.imports_not_in_target.is_empty()
27+
&& report.missing_exports.is_empty()
28+
&& report.mismatched_types.is_empty()
29+
{
30+
Ok(())
31+
} else {
32+
Err(report)
33+
}
34+
}
35+
}
36+
37+
impl fmt::Display for TargetValidationReport {
38+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39+
if !self.imports_not_in_target.is_empty() {
40+
writeln!(
41+
f,
42+
"Imports present in the component but not in the target world:"
43+
)?;
44+
for import in &self.imports_not_in_target {
45+
writeln!(f, " - {}", import)?;
46+
}
47+
}
48+
if !self.missing_exports.is_empty() {
49+
writeln!(
50+
f,
51+
"Exports required by target world but not present in the component:"
52+
)?;
53+
for (name, item_kind) in &self.missing_exports {
54+
writeln!(f, " - {}: {:?}", name, item_kind)?;
55+
}
56+
}
57+
if !self.mismatched_types.is_empty() {
58+
writeln!(
59+
f,
60+
"Type mismatches between the target world and the component:"
61+
)?;
62+
for (name, (kind, error)) in &self.mismatched_types {
63+
writeln!(f, " - {}: {:?} ({})", name, kind, error)?;
64+
}
65+
}
66+
Ok(())
67+
}
68+
}
69+
70+
impl std::error::Error for TargetValidationReport {}
71+
72+
impl TargetValidationReport {
73+
/// Returns the set of imports present in the component but not in the target world.
74+
pub fn imports_not_in_target(&self) -> impl Iterator<Item = &str> {
75+
self.imports_not_in_target.iter().map(|s| s.as_str())
76+
}
77+
78+
/// Returns the exports not in the component but required by the target world.
79+
pub fn missing_exports(&self) -> impl Iterator<Item = (&str, &ItemKind)> {
80+
self.missing_exports.iter().map(|(s, k)| (s.as_str(), k))
81+
}
82+
83+
/// Returns the mismatched types between the component and the target world.
84+
pub fn mismatched_types(&self) -> impl Iterator<Item = (&str, &ExternKind, &anyhow::Error)> {
85+
self.mismatched_types
86+
.iter()
87+
.map(|(s, (k, e))| (s.as_str(), k, e))
88+
}
89+
}
90+
91+
/// Validate whether the component conforms to the given world.
92+
///
93+
/// # Example
94+
///
95+
/// ```ignore
96+
/// let mut types = Types::default();
97+
///
98+
/// let mut resolve = wit_parser::Resolve::new();
99+
/// let pkg = resolve.push_dir(path_to_wit_dir)?.0;
100+
/// let wit_bytes = wit_component::encode(&resolve, pkg)?;
101+
/// let wit = Package::from_bytes("wit", None, wit_bytes, &mut types)?;
102+
///
103+
/// let component_bytes = std::fs::read(path_to_component)?;
104+
/// let component = Package::from_bytes("component", None, component_bytes, &mut types)?;
105+
/// let wit_world = get_wit_world(&types, wit.ty())?;
106+
///
107+
/// validate_target(&types, wit_world, component.ty())?;
108+
/// ```
109+
pub fn validate_target(
110+
types: &Types,
111+
wit_world_id: WorldId,
112+
component_world_id: WorldId,
113+
) -> TargetValidationResult {
114+
let component_world = &types[component_world_id];
115+
let wit_world = &types[wit_world_id];
116+
// The interfaces imported implicitly through uses.
117+
let implicit_imported_interfaces = wit_world.implicit_imported_interfaces(types);
118+
let mut cache = Default::default();
119+
let mut checker = SubtypeChecker::new(&mut cache);
120+
121+
let mut report = TargetValidationReport::default();
122+
123+
// The output is allowed to import a subset of the world's imports
124+
checker.invert();
125+
for (import, item_kind) in component_world.imports.iter() {
126+
let Some(expected) = implicit_imported_interfaces
127+
.get(import.as_str())
128+
.or_else(|| wit_world.imports.get(import))
129+
else {
130+
report.imports_not_in_target.insert(import.to_owned());
131+
continue;
132+
};
133+
134+
if let Err(e) = checker.is_subtype(expected.promote(), types, *item_kind, types) {
135+
report
136+
.mismatched_types
137+
.insert(import.to_owned(), (ExternKind::Import, e));
138+
}
139+
}
140+
141+
checker.revert();
142+
143+
// The output must export every export in the world
144+
for (name, expected) in &wit_world.exports {
145+
let Some(export) = component_world.exports.get(name).copied() else {
146+
report.missing_exports.insert(name.to_owned(), *expected);
147+
continue;
148+
};
149+
150+
if let Err(e) = checker.is_subtype(export, types, expected.promote(), types) {
151+
report
152+
.mismatched_types
153+
.insert(name.to_owned(), (ExternKind::Export, e));
154+
}
155+
}
156+
157+
report.into()
158+
}

src/commands/targets.rs

Lines changed: 1 addition & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{
44
fs,
55
path::{Path, PathBuf},
66
};
7-
use wac_types::{ExternKind, ItemKind, Package, SubtypeChecker, Types, WorldId};
7+
use wac_types::{validate_target, ItemKind, Package, Types, WorldId};
88

99
/// Verifies that a given WebAssembly component targets a world.
1010
#[derive(Args)]
@@ -105,89 +105,3 @@ fn encode_wit_as_component(path: &Path) -> anyhow::Result<Vec<u8>> {
105105
})?;
106106
Ok(encoded)
107107
}
108-
109-
/// An error in target validation
110-
#[derive(thiserror::Error, miette::Diagnostic, Debug)]
111-
#[diagnostic(code("component does not match wit world"))]
112-
pub enum Error {
113-
#[error("the target wit does not have an import named `{import}` but the component does")]
114-
/// The import is not in the target world
115-
ImportNotInTarget {
116-
/// The name of the missing target
117-
import: String,
118-
},
119-
#[error("{kind} `{name}` has a mismatched type for targeted wit world")]
120-
/// An import or export has a mismatched type for the target world.
121-
TargetMismatch {
122-
/// The name of the mismatched item
123-
name: String,
124-
/// The extern kind of the item
125-
kind: ExternKind,
126-
/// The source of the error
127-
#[source]
128-
source: anyhow::Error,
129-
},
130-
#[error("the targeted wit world requires an export named `{name}` but the component did not export one")]
131-
/// Missing an export for the target world.
132-
MissingTargetExport {
133-
/// The export name.
134-
name: String,
135-
/// The expected item kind.
136-
kind: ItemKind,
137-
},
138-
}
139-
140-
/// Validate whether the component conforms to the given world
141-
pub fn validate_target(
142-
types: &Types,
143-
wit_world_id: WorldId,
144-
component_world_id: WorldId,
145-
) -> Result<(), Error> {
146-
let component_world = &types[component_world_id];
147-
let wit_world = &types[wit_world_id];
148-
// The interfaces imported implicitly through uses.
149-
let implicit_imported_interfaces = wit_world.implicit_imported_interfaces(types);
150-
let mut cache = Default::default();
151-
let mut checker = SubtypeChecker::new(&mut cache);
152-
153-
// The output is allowed to import a subset of the world's imports
154-
checker.invert();
155-
for (import, item_kind) in component_world.imports.iter() {
156-
let expected = implicit_imported_interfaces
157-
.get(import.as_str())
158-
.or_else(|| wit_world.imports.get(import))
159-
.ok_or_else(|| Error::ImportNotInTarget {
160-
import: import.to_owned(),
161-
})?;
162-
163-
checker
164-
.is_subtype(expected.promote(), types, *item_kind, types)
165-
.map_err(|e| Error::TargetMismatch {
166-
kind: ExternKind::Import,
167-
name: import.to_owned(),
168-
source: e,
169-
})?;
170-
}
171-
172-
checker.revert();
173-
174-
// The output must export every export in the world
175-
for (name, expected) in &wit_world.exports {
176-
let export = component_world.exports.get(name).copied().ok_or_else(|| {
177-
Error::MissingTargetExport {
178-
name: name.clone(),
179-
kind: *expected,
180-
}
181-
})?;
182-
183-
checker
184-
.is_subtype(export, types, expected.promote(), types)
185-
.map_err(|e| Error::TargetMismatch {
186-
kind: ExternKind::Export,
187-
name: name.clone(),
188-
source: e,
189-
})?;
190-
}
191-
192-
Ok(())
193-
}

0 commit comments

Comments
 (0)