Skip to main content

morphir_core/metadata/
provider.rs

1//! Interpret the finite built-in declaration vocabulary in native V4 facts.
2//!
3//! This module is pure. Its caller must authenticate the exact provider IR and
4//! every context resource before using the returned closure as trusted meaning.
5
6use super::admission::{
7    AdmissionError, Interpretation, Interpreter, NodeTargetKind, ObjectDeclaration,
8    PredicateClosure, PredicateDeclaration, SubjectRole,
9};
10use super::{ContextResources, DocumentId, Fact, ObjectTerm};
11use crate::ir::v4::{self, Access, Distribution};
12use crate::naming::FQName;
13use crate::node_address::{NodeIndex, NodeOwner, NodeResolutionError, NodeRoot, NodeUri};
14use std::collections::BTreeMap;
15
16const SCHEMA_VOCAB: &str = "morphir://ir/pkg/morphir/metadata?format=4.1.0#/module/schema/value/";
17
18/// A native declaration is malformed or does not name a public provider value.
19#[derive(Debug, thiserror::Error)]
20pub enum ProviderDeclarationError {
21    #[error("native predicate declarations require V4.1 IR")]
22    Format,
23    #[error("cannot expand provider declaration facts: {0}")]
24    Graph(#[from] v4::DocumentGraphError),
25    #[error("cannot index provider declarations: {0}")]
26    Index(#[from] NodeResolutionError),
27    #[error("invalid provider document identity: {0}")]
28    Document(String),
29    #[error("native predicate declaration must name a public value with an output type")]
30    MissingPublicValue,
31    #[error("invalid native predicate declaration: {0}")]
32    Invalid(&'static str),
33    #[error(transparent)]
34    Admission(#[from] AdmissionError),
35}
36
37impl PredicateClosure {
38    /// Read native declaration facts in an already supplied V4 provider.
39    ///
40    /// This pure operation does not authenticate the provider. A consumer must
41    /// obtain the exact IR and context bytes from its fresh verified Library
42    /// restore before treating the resulting closure as trusted.
43    pub fn from_v4_provider(
44        file: &v4::IRFile,
45        resources: &ContextResources,
46    ) -> Result<Self, ProviderDeclarationError> {
47        if !matches!(&file.format_version, v4::FormatVersion::String(version) if version == "4.1.0")
48        {
49            return Err(ProviderDeclarationError::Format);
50        }
51        let index = NodeIndex::v4_file(file)?;
52        let owner = index.address_for(&NodeRoot::Distribution, &[])?;
53        let owner = DocumentId::new(owner.to_string())
54            .map_err(|error| ProviderDeclarationError::Document(error.to_string()))?;
55        // Only the schema vocabulary is interpreted here. Other @json facts
56        // may need their own declaration and must not create a bootstrap cycle.
57        let graph = v4::expand_v4_single_file_graph(file, &owner, resources, |predicate| {
58            Some(predicate.clone())
59        })?;
60        let mut groups: BTreeMap<String, Vec<&Fact>> = BTreeMap::new();
61        for fact in graph.facts() {
62            if fact.predicate().to_string().starts_with(SCHEMA_VOCAB) {
63                groups
64                    .entry(fact.subject().to_string())
65                    .or_default()
66                    .push(fact);
67            }
68        }
69        let declarations = groups
70            .into_values()
71            .map(|facts| declaration(file, &index, &facts))
72            .collect::<Result<Vec<_>, _>>()?;
73        Ok(Self::new(declarations)?)
74    }
75}
76
77fn declaration(
78    file: &v4::IRFile,
79    index: &NodeIndex,
80    facts: &[&Fact],
81) -> Result<PredicateDeclaration, ProviderDeclarationError> {
82    let uri = facts[0].subject();
83    let NodeRoot::Value {
84        owner: NodeOwner::OwnPackage,
85        module,
86        name,
87    } = uri.root()
88    else {
89        return Err(ProviderDeclarationError::MissingPublicValue);
90    };
91    if !uri.steps().is_empty() || index.address_for(uri.root(), &[])? != *uri {
92        return Err(ProviderDeclarationError::MissingPublicValue);
93    }
94    let module_name = module.to_canonical_string();
95    let value_name = name.to_canonical_string();
96    let output = match &file.distribution {
97        Distribution::Library(library) => library
98            .def
99            .modules
100            .get(&module_name)
101            .filter(|item| item.access == Access::Public)
102            .and_then(|item| item.value.values.get(&value_name))
103            .filter(|item| item.access == Access::Public)
104            .and_then(|item| item.value.value.output_type.as_ref()),
105        Distribution::Specs(specs) => specs
106            .spec
107            .modules
108            .get(&module_name)
109            .and_then(|item| item.values.get(&value_name))
110            .map(|item| &item.value.output),
111        Distribution::Application(_) => None,
112    }
113    .ok_or(ProviderDeclarationError::MissingPublicValue)?;
114    let mut roles = Vec::new();
115    let mut object_form = None;
116    let mut node_target_kind = None;
117    let mut interpreter = None;
118    for fact in facts {
119        let predicate = fact.predicate().to_string();
120        let term = predicate
121            .strip_prefix(SCHEMA_VOCAB)
122            .ok_or(ProviderDeclarationError::Invalid("schema predicate"))?;
123        let ObjectTerm::Value(value) = fact.object() else {
124            return Err(ProviderDeclarationError::Invalid(
125                "schema object must be an untyped string literal",
126            ));
127        };
128        if value.datatype().is_some() {
129            return Err(ProviderDeclarationError::Invalid(
130                "schema object must be an untyped string literal",
131            ));
132        }
133        let value = value
134            .value()
135            .as_str()
136            .ok_or(ProviderDeclarationError::Invalid(
137                "schema object must be an untyped string literal",
138            ))?;
139        match term {
140            "subject-role" => roles.push(subject_role(value)?),
141            "object-form" => set_once(&mut object_form, value)?,
142            "node-target-kind" => set_once(&mut node_target_kind, value)?,
143            "interpreter" => set_once(&mut interpreter, value)?,
144            _ => return Err(ProviderDeclarationError::Invalid("unknown schema term")),
145        }
146    }
147    let object =
148        match object_form.ok_or(ProviderDeclarationError::Invalid("missing object form"))? {
149            "data" => ObjectDeclaration::data(output.clone()),
150            "json" => ObjectDeclaration::json(json_type_uri(file, index, output)?, output.clone()),
151            "node" => {
152                let sdk_string = FQName::from_canonical_string("morphir/SDK:string#string")
153                    .expect("built-in String name is valid");
154                if !matches!(output, v4::Type::Reference(_, name, arguments)
155                    if name == &sdk_string && arguments.is_empty())
156                {
157                    return Err(ProviderDeclarationError::Invalid(
158                        "node reference output must be String",
159                    ));
160                }
161                let kind = match node_target_kind.ok_or(ProviderDeclarationError::Invalid(
162                    "missing node target kind",
163                ))? {
164                    "Type" => NodeTargetKind::Type,
165                    "Value" => NodeTargetKind::Value,
166                    "Module" => NodeTargetKind::Module,
167                    "Package" => NodeTargetKind::Package,
168                    _ => {
169                        return Err(ProviderDeclarationError::Invalid(
170                            "unknown node target kind",
171                        ));
172                    }
173                };
174                ObjectDeclaration::node(kind)
175            }
176            _ => return Err(ProviderDeclarationError::Invalid("unsupported object form")),
177        };
178    if object_form != Some("node") && node_target_kind.is_some() {
179        return Err(ProviderDeclarationError::Invalid(
180            "node target kind requires node object form",
181        ));
182    }
183    let interpretation = match interpreter
184        .ok_or(ProviderDeclarationError::Invalid("missing interpretation"))?
185    {
186        "descriptive" => Interpretation::Descriptive,
187        "target-name-language-ids" => Interpretation::Required(Interpreter::TargetNameLanguageIds),
188        _ => return Err(ProviderDeclarationError::Invalid("unsupported interpreter")),
189    };
190    Ok(PredicateDeclaration::value(
191        uri.clone(),
192        object,
193        roles,
194        interpretation,
195    ))
196}
197
198fn json_type_uri(
199    file: &v4::IRFile,
200    index: &NodeIndex,
201    output: &v4::Type,
202) -> Result<NodeUri, ProviderDeclarationError> {
203    let v4::Type::Reference(_, name, arguments) = output else {
204        return Err(ProviderDeclarationError::Invalid(
205            "JSON output must name a type",
206        ));
207    };
208    if !arguments.is_empty() || &name.package_path != file.distribution.package_name().as_path() {
209        return Err(ProviderDeclarationError::Invalid(
210            "JSON output must name an unparameterized type in this provider",
211        ));
212    }
213    let module_name = name.module_path.to_canonical_string();
214    let type_name = name.local_name.to_canonical_string();
215    let public = match &file.distribution {
216        Distribution::Library(library) => library
217            .def
218            .modules
219            .get(&module_name)
220            .filter(|item| item.access == Access::Public)
221            .and_then(|item| item.value.types.get(&type_name))
222            .is_some_and(|item| item.access == Access::Public),
223        Distribution::Specs(specs) => specs
224            .spec
225            .modules
226            .get(&module_name)
227            .is_some_and(|item| item.types.contains_key(&type_name)),
228        Distribution::Application(_) => false,
229    };
230    if !public {
231        return Err(ProviderDeclarationError::Invalid(
232            "JSON type must be public",
233        ));
234    }
235    Ok(index.address_for(
236        &NodeRoot::Type {
237            owner: NodeOwner::OwnPackage,
238            module: name.module_path.clone(),
239            name: name.local_name.clone(),
240        },
241        &[],
242    )?)
243}
244
245fn set_once<'a>(
246    slot: &mut Option<&'a str>,
247    value: &'a str,
248) -> Result<(), ProviderDeclarationError> {
249    if slot.replace(value).is_some() {
250        return Err(ProviderDeclarationError::Invalid(
251            "duplicate schema property",
252        ));
253    }
254    Ok(())
255}
256
257fn subject_role(value: &str) -> Result<SubjectRole, ProviderDeclarationError> {
258    match value {
259        "TypeSpecification" => Ok(SubjectRole::TypeSpecification),
260        "TypeDefinition" => Ok(SubjectRole::TypeDefinition),
261        "ValueSpecification" => Ok(SubjectRole::ValueSpecification),
262        "ValueDefinition" => Ok(SubjectRole::ValueDefinition),
263        "TypeExpression" => Ok(SubjectRole::TypeExpression),
264        "ValueExpression" => Ok(SubjectRole::ValueExpression),
265        "Pattern" => Ok(SubjectRole::Pattern),
266        "Module" => Ok(SubjectRole::Module),
267        "Package" => Ok(SubjectRole::Package),
268        _ => Err(ProviderDeclarationError::Invalid("unknown subject role")),
269    }
270}