use super::*;
use std::sync::OnceLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LinkReport {
pub catalog_ids_checked: usize,
pub domains_checked: usize,
}
pub(crate) struct CompilerContract {
pub(super) catalog: Catalog,
pub(super) manifest: &'static Manifest,
pub(super) links: LinkReport,
}
static COMPILER_CONTRACT: OnceLock<Result<CompilerContract, IntegrationError>> = OnceLock::new();
pub(crate) fn cross_check_manifest(
manifest: &Manifest,
catalog: &Catalog,
) -> Result<LinkReport, IntegrationError> {
#[cfg(test)]
crate::resource_metrics::record_contract_check();
let mut catalog_ids_checked = 0;
let mut domains_checked = 0;
for function in &manifest.functions {
if let Some(catalog_id) = &function.catalog_id {
let kind = match function.kind {
FunctionKind::Action | FunctionKind::MemberAction => Kind::Action,
FunctionKind::Value | FunctionKind::MemberValue => Kind::Value,
};
catalog_ids_checked += 1;
if catalog.entry(kind, catalog_id).is_none() {
return Err(IntegrationError::new(
"catalog-link-missing",
format!(
"manifest function '{}' links to missing {:?} catalog id '{}'",
function.id, kind, catalog_id
),
None,
));
}
}
for parameter in &function.params {
let Some(domain) = ¶meter.domain else {
continue;
};
if crate::lower::policy::is_contextual_domain(domain) {
continue;
}
domains_checked += 1;
if catalog.enum_domain(domain).is_none() {
return Err(IntegrationError::new(
"domain-link-missing",
format!(
"manifest function '{}' parameter '{}' links to missing enum domain '{}',",
function.id, parameter.name, domain
),
None,
));
}
}
if let Some(contextual) = crate::lower::policy::contextual_domain(&function.id) {
for option in contextual.options {
domains_checked += 1;
if catalog.enum_domain(option.domain).is_none() {
return Err(IntegrationError::new(
"domain-link-missing",
format!(
"manifest function '{}' contextual option links to missing enum domain '{}'",
function.id, option.domain
),
None,
));
}
}
}
}
Ok(LinkReport {
catalog_ids_checked,
domains_checked,
})
}
pub(crate) fn load_compiler_contract() -> Result<&'static CompilerContract, IntegrationError> {
COMPILER_CONTRACT
.get_or_init(|| {
let catalog = Catalog::builtin()
.map_err(|error| IntegrationError::new("catalog-load", error.to_string(), None))?;
let manifest = Manifest::builtin()
.map_err(|error| IntegrationError::new("manifest-load", error.to_string(), None))?;
let links = cross_check_manifest(manifest, &catalog)?;
Ok(CompilerContract {
catalog,
manifest,
links,
})
})
.as_ref()
.map_err(Clone::clone)
}