Skip to main content

opy_rs/compiler/
integration.rs

1//! Manifest/catalog validation and the released Workshop integration contract.
2
3use super::*;
4
5/// Results of the manifest-to-catalog cross-check.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct LinkReport {
8    pub catalog_ids_checked: usize,
9    pub domains_checked: usize,
10}
11
12/// Cross-check every OPY manifest `catalogId` and domain identity against the
13/// canonical Workshop catalog. No local catalog copy or spelling allowlist is
14/// involved.
15pub(crate) fn cross_check_manifest(
16    manifest: &Manifest,
17    catalog: &Catalog,
18) -> Result<LinkReport, IntegrationError> {
19    #[cfg(test)]
20    crate::resource_metrics::record_contract_check();
21    let mut catalog_ids_checked = 0;
22    let mut domains_checked = 0;
23
24    for function in &manifest.functions {
25        if let Some(catalog_id) = &function.catalog_id {
26            let kind = match function.kind {
27                FunctionKind::Action | FunctionKind::MemberAction => Kind::Action,
28                FunctionKind::Value | FunctionKind::MemberValue => Kind::Value,
29            };
30            catalog_ids_checked += 1;
31            if catalog.entry(kind, catalog_id).is_none() {
32                return Err(IntegrationError::new(
33                    "catalog-link-missing",
34                    format!(
35                        "manifest function '{}' links to missing {:?} catalog id '{}'",
36                        function.id, kind, catalog_id
37                    ),
38                    None,
39                ));
40            }
41        }
42
43        for parameter in &function.params {
44            let Some(domain) = &parameter.domain else {
45                continue;
46            };
47            if crate::lower::policy::is_contextual_domain(domain) {
48                continue;
49            }
50            domains_checked += 1;
51            if catalog.enum_domain(domain).is_none() {
52                return Err(IntegrationError::new(
53                    "domain-link-missing",
54                    format!(
55                        "manifest function '{}' parameter '{}' links to missing enum domain '{}',",
56                        function.id, parameter.name, domain
57                    ),
58                    None,
59                ));
60            }
61        }
62
63        if let Some(contextual) = crate::lower::policy::contextual_domain(&function.id) {
64            for option in contextual.options {
65                domains_checked += 1;
66                if catalog.enum_domain(option.domain).is_none() {
67                    return Err(IntegrationError::new(
68                        "domain-link-missing",
69                        format!(
70                            "manifest function '{}' contextual option links to missing enum domain '{}'",
71                            function.id, option.domain
72                        ),
73                        None,
74                    ));
75                }
76            }
77        }
78    }
79
80    Ok(LinkReport {
81        catalog_ids_checked,
82        domains_checked,
83    })
84}
85
86pub(crate) fn load_compiler_contract()
87-> Result<(Catalog, &'static Manifest, LinkReport), IntegrationError> {
88    let catalog = Catalog::builtin()
89        .map_err(|error| IntegrationError::new("catalog-load", error.to_string(), None))?;
90    let manifest = Manifest::builtin()
91        .map_err(|error| IntegrationError::new("manifest-load", error.to_string(), None))?;
92    let links = cross_check_manifest(manifest, &catalog)?;
93    Ok((catalog, manifest, links))
94}