1use crate::{ExternKind, ItemKind, SubtypeChecker, Types, WorldId};
2use std::collections::{BTreeMap, BTreeSet};
3use std::fmt;
4
5pub type TargetValidationResult = Result<(), TargetValidationReport>;
7
8#[derive(Debug, Default)]
10pub struct TargetValidationReport {
11 imports_not_in_target: BTreeSet<String>,
13 missing_exports: BTreeMap<String, ItemKind>,
17 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 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 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 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
91pub 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 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 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 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}