1use crate::{
2 ExternKind, ItemKind, NameMap, NameMapNoIntern, SubtypeChecker, Types, World, WorldId,
3};
4use std::collections::{BTreeMap, BTreeSet};
5use std::fmt;
6
7pub type TargetValidationResult = Result<(), TargetValidationReport>;
9
10#[derive(Debug, Default)]
12pub struct TargetValidationReport {
13 imports_not_in_target: BTreeSet<String>,
15 missing_exports: BTreeMap<String, ItemKind>,
19 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 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 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 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
93pub 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 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 map.insert(name, &mut NameMapNoIntern, true, *item).unwrap();
149 map
150 });
151
152 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 fn all_imports(&self, types: &Types) -> NameMap<String, ItemKind> {
174 let mut map = NameMap::default();
175 let mut intern = NameMapNoIntern;
176 for (name, kind) in self.implicit_imported_interfaces(types) {
178 map.insert(name, &mut intern, true, kind).unwrap();
180 }
181 for (name, item) in &self.imports {
183 map.insert(name, &mut intern, true, *item).unwrap();
185 }
186 map
187 }
188}