1use 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#[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 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 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}