use crate::{
ExternKind, ItemKind, NameMap, NameMapNoIntern, SubtypeChecker, Types, World, WorldId,
};
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
pub type TargetValidationResult = Result<(), TargetValidationReport>;
#[derive(Debug, Default)]
pub struct TargetValidationReport {
imports_not_in_target: BTreeSet<String>,
missing_exports: BTreeMap<String, ItemKind>,
mismatched_types: BTreeMap<String, (ExternKind, anyhow::Error)>,
}
impl From<TargetValidationReport> for TargetValidationResult {
fn from(report: TargetValidationReport) -> TargetValidationResult {
if report.imports_not_in_target.is_empty()
&& report.missing_exports.is_empty()
&& report.mismatched_types.is_empty()
{
Ok(())
} else {
Err(report)
}
}
}
impl fmt::Display for TargetValidationReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.imports_not_in_target.is_empty() {
writeln!(
f,
"Imports present in the component but not in the target world:"
)?;
for import in &self.imports_not_in_target {
writeln!(f, " - {}", import)?;
}
}
if !self.missing_exports.is_empty() {
writeln!(
f,
"Exports required by target world but not present in the component:"
)?;
for (name, item_kind) in &self.missing_exports {
writeln!(f, " - {}: {:?}", name, item_kind)?;
}
}
if !self.mismatched_types.is_empty() {
writeln!(
f,
"Type mismatches between the target world and the component:"
)?;
for (name, (kind, error)) in &self.mismatched_types {
writeln!(f, " - {}: {:?} ({})", name, kind, error)?;
}
}
Ok(())
}
}
impl std::error::Error for TargetValidationReport {}
impl TargetValidationReport {
pub fn imports_not_in_target(&self) -> impl Iterator<Item = &str> {
self.imports_not_in_target.iter().map(|s| s.as_str())
}
pub fn missing_exports(&self) -> impl Iterator<Item = (&str, &ItemKind)> {
self.missing_exports.iter().map(|(s, k)| (s.as_str(), k))
}
pub fn mismatched_types(&self) -> impl Iterator<Item = (&str, &ExternKind, &anyhow::Error)> {
self.mismatched_types
.iter()
.map(|(s, (k, e))| (s.as_str(), k, e))
}
}
pub fn validate_target(
types: &Types,
wit_world_id: WorldId,
component_world_id: WorldId,
) -> TargetValidationResult {
let component_world = &types[component_world_id];
let wit_world = &types[wit_world_id];
let mut cache = Default::default();
let mut checker = SubtypeChecker::new(&mut cache);
let mut report = TargetValidationReport::default();
checker.invert();
let world_imports = wit_world.all_imports(types);
for (import, item_kind) in component_world.imports.iter() {
let Some(expected) = world_imports.get(import.as_str(), &NameMapNoIntern) else {
report.imports_not_in_target.insert(import.to_owned());
continue;
};
if let Err(e) = checker.is_subtype(expected.promote(), types, *item_kind, types) {
report
.mismatched_types
.insert(import.to_owned(), (ExternKind::Import, e));
}
}
checker.revert();
let component_exports =
component_world
.exports
.iter()
.fold(NameMap::default(), |mut map, (name, item)| {
map.insert(name, &mut NameMapNoIntern, true, *item).unwrap();
map
});
for (name, expected) in &wit_world.exports {
let Some(export) = component_exports.get(name, &NameMapNoIntern).copied() else {
report.missing_exports.insert(name.to_owned(), *expected);
continue;
};
if let Err(e) = checker.is_subtype(export, types, expected.promote(), types) {
report
.mismatched_types
.insert(name.to_owned(), (ExternKind::Export, e));
}
}
report.into()
}
impl World {
fn all_imports(&self, types: &Types) -> NameMap<String, ItemKind> {
let mut map = NameMap::default();
let mut intern = NameMapNoIntern;
for (name, kind) in self.implicit_imported_interfaces(types) {
map.insert(name, &mut intern, true, kind).unwrap();
}
for (name, item) in &self.imports {
map.insert(name, &mut intern, true, *item).unwrap();
}
map
}
}