wac_types/
targets.rs

1use crate::{ExternKind, ItemKind, SubtypeChecker, Types, WorldId};
2use std::collections::{BTreeMap, BTreeSet};
3use std::fmt;
4
5/// The result of validating a component against a target world.
6pub type TargetValidationResult = Result<(), TargetValidationReport>;
7
8/// The report of validating a component against a target world.
9#[derive(Debug, Default)]
10pub 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
24impl 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
37impl 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
70impl std::error::Error for TargetValidationReport {}
71
72impl 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/// ```
109pub 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}