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    let mut catalog_ids_checked = 0;
20    let mut domains_checked = 0;
21
22    for function in &manifest.functions {
23        if let Some(catalog_id) = &function.catalog_id {
24            let kind = match function.kind {
25                FunctionKind::Action | FunctionKind::MemberAction => Kind::Action,
26                FunctionKind::Value | FunctionKind::MemberValue => Kind::Value,
27            };
28            catalog_ids_checked += 1;
29            if catalog.entry(kind, catalog_id).is_none() {
30                return Err(IntegrationError::new(
31                    "catalog-link-missing",
32                    format!(
33                        "manifest function '{}' links to missing {:?} catalog id '{}'",
34                        function.id, kind, catalog_id
35                    ),
36                    None,
37                ));
38            }
39        }
40
41        for parameter in &function.params {
42            let Some(domain) = &parameter.domain else {
43                continue;
44            };
45            if crate::lower::policy::is_contextual_domain(domain) {
46                continue;
47            }
48            domains_checked += 1;
49            if catalog.enum_domain(domain).is_none() {
50                return Err(IntegrationError::new(
51                    "domain-link-missing",
52                    format!(
53                        "manifest function '{}' parameter '{}' links to missing enum domain '{}',",
54                        function.id, parameter.name, domain
55                    ),
56                    None,
57                ));
58            }
59        }
60
61        if let Some(contextual) = crate::lower::policy::contextual_domain(&function.id) {
62            for option in contextual.options {
63                domains_checked += 1;
64                if catalog.enum_domain(option.domain).is_none() {
65                    return Err(IntegrationError::new(
66                        "domain-link-missing",
67                        format!(
68                            "manifest function '{}' contextual option links to missing enum domain '{}'",
69                            function.id, option.domain
70                        ),
71                        None,
72                    ));
73                }
74            }
75        }
76    }
77
78    Ok(LinkReport {
79        catalog_ids_checked,
80        domains_checked,
81    })
82}
83
84pub(crate) fn load_compiler_contract()
85-> Result<(Catalog, &'static Manifest, LinkReport), IntegrationError> {
86    let catalog = Catalog::builtin()
87        .map_err(|error| IntegrationError::new("catalog-load", error.to_string(), None))?;
88    let manifest = Manifest::builtin()
89        .map_err(|error| IntegrationError::new("manifest-load", error.to_string(), None))?;
90    let links = cross_check_manifest(manifest, &catalog)?;
91    Ok((catalog, manifest, links))
92}