1use super::{Carrier, Fact, ObjectTerm};
8use crate::data_value::{DataValueError, DataValueValidator};
9use crate::ir::v4;
10use crate::naming::FQName;
11use crate::node_address::{NodeRoot, NodeUri};
12use serde_json::Value;
13use std::collections::{BTreeMap, BTreeSet};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
17pub enum SubjectRole {
18 TypeSpecification,
19 TypeDefinition,
20 ValueSpecification,
21 ValueDefinition,
22 TypeExpression,
23 ValueExpression,
24 Pattern,
25 Module,
26 Package,
27}
28
29#[derive(Debug, Clone)]
31pub enum ObjectDeclaration {
32 Data(v4::Type),
33 Json {
34 datatype: NodeUri,
35 ty: DeclaredDataType,
36 },
37 Node(NodeTargetKind),
38}
39
40impl ObjectDeclaration {
41 pub fn data(ty: v4::Type) -> Self {
42 Self::Data(ty)
43 }
44
45 pub fn json(datatype: NodeUri, ty: v4::Type) -> Self {
47 Self::Json {
48 datatype,
49 ty: DeclaredDataType::V4(Box::new(ty)),
50 }
51 }
52
53 pub fn sidecar_json(datatype: NodeUri, entry_point: FQName) -> Self {
55 Self::Json {
56 datatype,
57 ty: DeclaredDataType::V3(entry_point),
58 }
59 }
60
61 pub fn node(target: NodeTargetKind) -> Self {
62 Self::Node(target)
63 }
64}
65
66#[derive(Debug, Clone)]
67pub enum DeclaredDataType {
68 V3(FQName),
69 V4(Box<v4::Type>),
70}
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum NodeTargetKind {
75 Type,
76 Value,
77 Module,
78 Package,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum Interpreter {
84 TargetNameLanguageIds,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum Interpretation {
90 Descriptive,
91 Required(Interpreter),
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95enum DeclarationRole {
96 ValueSpecification,
97 SidecarTypeSpecification,
98}
99
100#[derive(Debug, Clone)]
102pub struct PredicateDeclaration {
103 uri: NodeUri,
104 role: DeclarationRole,
105 object: ObjectDeclaration,
106 subjects: BTreeSet<SubjectRole>,
107 interpretation: Interpretation,
108}
109
110impl PredicateDeclaration {
111 pub fn value(
112 uri: NodeUri,
113 object: ObjectDeclaration,
114 subjects: impl IntoIterator<Item = SubjectRole>,
115 interpretation: Interpretation,
116 ) -> Self {
117 Self {
118 uri,
119 role: DeclarationRole::ValueSpecification,
120 object,
121 subjects: subjects.into_iter().collect(),
122 interpretation,
123 }
124 }
125
126 pub fn sidecar_type(
128 uri: NodeUri,
129 object: ObjectDeclaration,
130 subjects: impl IntoIterator<Item = SubjectRole>,
131 ) -> Self {
132 Self {
133 uri,
134 role: DeclarationRole::SidecarTypeSpecification,
135 object,
136 subjects: subjects.into_iter().collect(),
137 interpretation: Interpretation::Descriptive,
138 }
139 }
140
141 pub fn json_datatype(&self) -> Option<&NodeUri> {
143 match &self.object {
144 ObjectDeclaration::Json { datatype, .. } => Some(datatype),
145 _ => None,
146 }
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152pub enum Admission {
153 Validated,
154 PreservedUnvalidated(UnvalidatedReason),
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub enum UnvalidatedReason {
160 PredicateDeclaration,
161 Target,
162 RequiredInterpreter(Interpreter),
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
167pub enum AdmissionError {
168 #[error("predicate URI root does not match its declaration role")]
169 InvalidDeclarationRole,
170 #[error("predicate closure contains a duplicate declaration")]
171 DuplicatePredicate,
172 #[error("@json datatype must name a type declaration")]
173 InvalidDatatypeDeclaration,
174 #[error("predicate declaration must allow at least one subject role")]
175 EmptySubjectRoles,
176 #[error("node-local carrier does not match the fact subject")]
177 CarrierSubjectMismatch,
178 #[error("type-specification predicate requires its matching sidecar carrier")]
179 DeclarationCarrierMismatch,
180 #[error("fact subject role is not allowed by predicate declaration")]
181 SubjectRoleMismatch,
182 #[error("predicate object does not match its declared object kind")]
183 ObjectKindMismatch,
184 #[error("typed JSON datatype does not match its predicate declaration")]
185 DatatypeMismatch,
186 #[error("node reference target kind does not match its predicate declaration")]
187 NodeTargetKindMismatch,
188 #[error("invalid language ID at {path}")]
189 InvalidLanguageId { path: String },
190 #[error("semantic interpreter received a value with the wrong structure")]
191 InterpreterInputMismatch,
192 #[error(transparent)]
193 DataValue(#[from] DataValueError),
194}
195
196#[derive(Debug, Default)]
198pub struct PredicateClosure {
199 declarations: BTreeMap<String, PredicateDeclaration>,
200}
201
202impl PredicateClosure {
203 pub fn new(
204 declarations: impl IntoIterator<Item = PredicateDeclaration>,
205 ) -> Result<Self, AdmissionError> {
206 let mut indexed = BTreeMap::new();
207 for declaration in declarations {
208 let expected_root = match declaration.role {
209 DeclarationRole::ValueSpecification => {
210 declaration.uri.format().major() == 4
211 && matches!(declaration.uri.root(), NodeRoot::Value { .. })
212 }
213 DeclarationRole::SidecarTypeSpecification => {
214 matches!(declaration.uri.format().major(), 3 | 4)
215 && matches!(declaration.uri.root(), NodeRoot::Type { .. })
216 && matches!(declaration.object, ObjectDeclaration::Json { .. })
217 }
218 };
219 if !expected_root || !declaration.uri.steps().is_empty() {
220 return Err(AdmissionError::InvalidDeclarationRole);
221 }
222 if let ObjectDeclaration::Json { datatype, ty } = &declaration.object {
223 let type_major = match ty {
224 DeclaredDataType::V3(_) => 3,
225 DeclaredDataType::V4(_) => 4,
226 };
227 if !matches!(datatype.root(), NodeRoot::Type { .. })
228 || !datatype.steps().is_empty()
229 || datatype.format().major() != type_major
230 || (matches!(ty, DeclaredDataType::V3(_))
231 && declaration.role != DeclarationRole::SidecarTypeSpecification)
232 {
233 return Err(AdmissionError::InvalidDatatypeDeclaration);
234 }
235 }
236 if declaration.subjects.is_empty() {
237 return Err(AdmissionError::EmptySubjectRoles);
238 }
239 let key = declaration.uri.to_string();
240 if indexed.insert(key, declaration).is_some() {
241 return Err(AdmissionError::DuplicatePredicate);
242 }
243 }
244 Ok(Self {
245 declarations: indexed,
246 })
247 }
248
249 pub fn json_datatype(&self, predicate: &NodeUri) -> Option<NodeUri> {
251 self.declarations
252 .get(&predicate.to_string())
253 .and_then(PredicateDeclaration::json_datatype)
254 .cloned()
255 }
256
257 pub fn admit(
259 &self,
260 fact: &Fact,
261 subject: SubjectRole,
262 carrier: &Carrier,
263 validator: &DataValueValidator,
264 available_interpreters: &[Interpreter],
265 target_kind: Option<NodeTargetKind>,
266 ) -> Result<Admission, AdmissionError> {
267 match carrier {
268 Carrier::AttributesFacts(uri) | Carrier::AnnotationsFacts(uri)
269 if uri != fact.subject() =>
270 {
271 return Err(AdmissionError::CarrierSubjectMismatch);
272 }
273 Carrier::Sidecar { target, .. } if target != fact.subject() => {
274 return Err(AdmissionError::CarrierSubjectMismatch);
275 }
276 _ => {}
277 }
278 let Some(declaration) = self.declarations.get(&fact.predicate().to_string()) else {
279 return Ok(Admission::PreservedUnvalidated(
280 UnvalidatedReason::PredicateDeclaration,
281 ));
282 };
283 match (declaration.role, carrier) {
284 (DeclarationRole::ValueSpecification, Carrier::Sidecar { .. }) => {
285 return Err(AdmissionError::DeclarationCarrierMismatch);
286 }
287 (DeclarationRole::SidecarTypeSpecification, Carrier::Sidecar { entry_point, .. })
288 if entry_point == fact.predicate() => {}
289 (DeclarationRole::SidecarTypeSpecification, _) => {
290 return Err(AdmissionError::DeclarationCarrierMismatch);
291 }
292 _ => {}
293 }
294 if !declaration.subjects.contains(&subject) {
295 return Err(AdmissionError::SubjectRoleMismatch);
296 }
297 match (&declaration.object, fact.object()) {
298 (ObjectDeclaration::Data(ty), ObjectTerm::Value(value))
299 if value.datatype().is_none() =>
300 {
301 validator.validate_v4_data(ty, value.value())?;
302 }
303 (ObjectDeclaration::Json { datatype, ty }, ObjectTerm::Value(value)) => {
304 if value.datatype() != Some(datatype) {
305 return Err(AdmissionError::DatatypeMismatch);
306 }
307 match ty {
308 DeclaredDataType::V3(entry_point) => {
309 validator.validate_reference(entry_point, value.value())?;
310 }
311 DeclaredDataType::V4(ty) => {
312 validator.validate_v4_data(ty, value.value())?;
313 }
314 }
315 }
316 (ObjectDeclaration::Node(expected), ObjectTerm::NodeRef(target)) => {
317 if !target.steps().is_empty() || !target_root_matches(*expected, target.root()) {
318 return Err(AdmissionError::NodeTargetKindMismatch);
319 }
320 match target_kind {
321 Some(actual) if actual == *expected => {}
322 Some(_) => return Err(AdmissionError::NodeTargetKindMismatch),
323 None => {
324 return Ok(Admission::PreservedUnvalidated(UnvalidatedReason::Target));
325 }
326 }
327 }
328 _ => return Err(AdmissionError::ObjectKindMismatch),
329 }
330 if let Interpretation::Required(interpreter) = declaration.interpretation {
331 if !available_interpreters.contains(&interpreter) {
332 return Ok(Admission::PreservedUnvalidated(
333 UnvalidatedReason::RequiredInterpreter(interpreter),
334 ));
335 }
336 match interpreter {
337 Interpreter::TargetNameLanguageIds => {
338 validate_target_name_language_ids(fact.object())?;
339 }
340 }
341 }
342 Ok(Admission::Validated)
343 }
344}
345
346fn target_root_matches(expected: NodeTargetKind, root: &NodeRoot) -> bool {
347 matches!(
348 (expected, root),
349 (NodeTargetKind::Type, NodeRoot::Type { .. })
350 | (NodeTargetKind::Value, NodeRoot::Value { .. })
351 | (NodeTargetKind::Module, NodeRoot::Module { .. })
352 | (NodeTargetKind::Package, NodeRoot::Package)
353 )
354}
355
356fn validate_target_name_language_ids(object: &ObjectTerm) -> Result<(), AdmissionError> {
357 let ObjectTerm::Value(value) = object else {
358 return Err(AdmissionError::InterpreterInputMismatch);
359 };
360 let Some(record) = value.value().as_object() else {
361 return Err(AdmissionError::InterpreterInputMismatch);
362 };
363 for field in ["frontend", "backend"] {
364 let Some(languages) = record.get(field).and_then(Value::as_object) else {
365 return Err(AdmissionError::InterpreterInputMismatch);
366 };
367 for language in languages.keys() {
368 if !valid_language_id(language) {
369 return Err(AdmissionError::InvalidLanguageId {
370 path: format!("$.{field}{}", json_member_path(language)),
371 });
372 }
373 }
374 }
375 Ok(())
376}
377
378fn valid_language_id(value: &str) -> bool {
379 let mut segments = value.split('-');
380 let Some(first) = segments.next() else {
381 return false;
382 };
383 let mut chars = first.bytes();
384 matches!(chars.next(), Some(b'a'..=b'z'))
385 && chars.all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit())
386 && segments.all(|segment| {
387 !segment.is_empty()
388 && segment
389 .bytes()
390 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit())
391 })
392}
393
394fn json_member_path(member: &str) -> String {
395 if member
396 .bytes()
397 .all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
398 && member
399 .bytes()
400 .next()
401 .is_some_and(|byte| byte.is_ascii_alphabetic() || byte == b'_')
402 {
403 format!(".{member}")
404 } else {
405 format!(
406 "[{}]",
407 serde_json::to_string(member).expect("JSON member names serialize")
408 )
409 }
410}