wac_types/
targets.rs

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