1use crate::diagnostics::{DiagLevel, Diagnostic};
10use crate::graph::{Loaded, term_to_node};
11use crate::path::parse_path;
12use crate::vocab;
13use oxrdf::{Literal, NamedNode, NamedOrBlankNode, Term};
14use shifty_algebra::{
15 Bound, NodeExpr, NodeKindSet, Path, Rule, RuleHead, Schema, Selector, Severity, Shape,
16 ShapeArena, ShapeId, SparqlConstraint, SparqlConstruct, SparqlQueryKind, SparqlTarget,
17 Statement, ValueType,
18};
19use spargebra::{Query, SparqlParser};
20use std::collections::{BTreeSet, HashMap, HashSet};
21
22pub struct Lowered {
23 pub schema: Schema,
24 pub diagnostics: Vec<Diagnostic>,
25}
26
27pub fn lower(g: &Loaded) -> Lowered {
29 let mut l = Lowerer {
30 g,
31 arena: ShapeArena::new(),
32 cache: HashMap::new(),
33 statements: Vec::new(),
34 rules: Vec::new(),
35 diags: Vec::new(),
36 };
37 let shapes = l.discover_shapes();
38 for s in &shapes {
39 l.lower_shape(s);
40 }
41 l.lower_custom_components(&shapes);
42 for s in &shapes {
43 let selectors = l.target_selectors(s);
45 if let Some(shape) = l.cache.get(s).copied() {
46 for sel in &selectors {
47 l.statements.push(Statement {
48 selector: sel.clone(),
49 shape,
50 });
51 }
52 }
53 l.parse_rules(s, &selectors);
54 }
55 let names = l
56 .cache
57 .iter()
58 .filter_map(|(node, id)| match node {
59 NamedOrBlankNode::NamedNode(n) => Some((*id, n.as_str().to_string())),
60 NamedOrBlankNode::BlankNode(_) => None,
61 })
62 .collect();
63 let schema = Schema {
64 arena: l.arena,
65 statements: l.statements,
66 rules: l.rules,
67 names,
68 };
69 schema.arena.debug_assert_finalized();
70 Lowered {
71 schema,
72 diagnostics: l.diags,
73 }
74}
75
76struct Lowerer<'a> {
77 g: &'a Loaded,
78 arena: ShapeArena,
79 cache: HashMap<NamedOrBlankNode, ShapeId>,
80 statements: Vec<Statement>,
81 rules: Vec<Rule>,
82 diags: Vec<Diagnostic>,
83}
84
85struct ComponentDef {
86 iri: NamedNode,
87 params: Vec<ParamDef>,
88 node_validator: Option<ValidatorDef>,
89 property_validator: Option<ValidatorDef>,
90 generic_validator: Option<ValidatorDef>,
91}
92
93struct ParamDef {
94 path: NamedNode,
95 var: String,
96 optional: bool,
97}
98
99struct ValidatorDef {
100 kind: SparqlQueryKind,
101 query: String,
102 messages: Vec<Term>,
103}
104
105fn local_name(iri: &str) -> &str {
108 iri.rsplit(['#', '/']).next().unwrap_or(iri)
109}
110
111impl Lowerer<'_> {
112 fn diag(&mut self, level: DiagLevel, msg: impl Into<String>, subj: &NamedOrBlankNode) {
113 self.diags
114 .push(Diagnostic::new(level, msg, Some(subj.to_string())));
115 }
116
117 fn discover_shapes(&self) -> Vec<NamedOrBlankNode> {
121 let mut found: HashSet<NamedOrBlankNode> = HashSet::new();
122 for triple in self.g.graph.iter() {
123 let p = triple.predicate;
124 let is_target = p == vocab::SH_TARGET_NODE
125 || p == vocab::SH_TARGET_CLASS
126 || p == vocab::SH_TARGET_SUBJECTS_OF
127 || p == vocab::SH_TARGET_OBJECTS_OF
128 || p == vocab::SH_TARGET;
129 if p == vocab::SH_PATH || p == vocab::SH_SPARQL || p == vocab::SH_RULE || is_target {
130 found.insert(triple.subject.into_owned());
131 }
132 if p == vocab::RDF_TYPE
133 && let Term::NamedNode(ty) = triple.object.into_owned()
134 && (ty.as_ref() == vocab::SH_NODE_SHAPE || ty.as_ref() == vocab::SH_PROPERTY_SHAPE)
135 {
136 found.insert(triple.subject.into_owned());
137 }
138 }
139 let mut shapes: Vec<NamedOrBlankNode> = found.into_iter().collect();
140 shapes.sort_by_key(|n| n.to_string());
141 shapes
142 }
143
144 fn lower_shape(&mut self, s: &NamedOrBlankNode) -> ShapeId {
145 if let Some(id) = self.cache.get(s) {
146 return *id;
147 }
148 let id = self.arena.reserve();
149 self.cache.insert(s.clone(), id);
150
151 if self.bool_prop(s, vocab::SH_DEACTIVATED) {
152 self.arena.set(id, Shape::Top);
153 return id;
154 }
155
156 let path = self.parse_shape_path(s);
157 let mut conjuncts: Vec<ShapeId> = Vec::new();
158
159 let value = self.collect_value_constraints(s);
162 if !value.is_empty() {
163 let value_phi = self.arena.and(value);
164 match &path {
165 Some(p) => {
166 let neg = self.arena.not(value_phi);
168 let c = self.arena.count(p.clone(), None, Some(0), neg);
169 conjuncts.push(c);
170 }
171 None => conjuncts.push(value_phi),
172 }
173 }
174
175 self.collect_path_constraints(s, path.as_ref(), &mut conjuncts);
176
177 if self.bool_prop(s, vocab::SH_CLOSED) {
178 let q = self.closed_allowed(s);
179 let c = self.arena.insert(Shape::Closed(q));
180 conjuncts.push(c);
181 }
182
183 for constraint_term in self.g.objects(s, vocab::SH_SPARQL) {
184 let Some(constraint_node) = term_to_node(&constraint_term) else {
185 self.diag(DiagLevel::Error, "sh:sparql must reference a resource", s);
186 continue;
187 };
188 let parsed = if let Some(Term::Literal(query)) =
189 self.g.object(&constraint_node, vocab::SH_SELECT)
190 {
191 self.canonical_sparql(&constraint_node, query.value(), ExpectedQuery::Select)
192 .map(|query| (SparqlQueryKind::Select, query))
193 } else if let Some(Term::Literal(query)) =
194 self.g.object(&constraint_node, vocab::SH_ASK)
195 {
196 self.canonical_sparql(&constraint_node, query.value(), ExpectedQuery::Ask)
197 .map(|query| (SparqlQueryKind::Ask, query))
198 } else {
199 self.diag(
200 DiagLevel::Error,
201 "sh:sparql constraint requires sh:select or sh:ask",
202 &constraint_node,
203 );
204 None
205 };
206 if let Some((kind, query)) = parsed {
207 let shape = Some(match s {
208 NamedOrBlankNode::NamedNode(n) => Term::NamedNode(n.clone()),
209 NamedOrBlankNode::BlankNode(b) => Term::BlankNode(b.clone()),
210 });
211 let mut messages: Vec<Term> = self.g.objects(&constraint_node, vocab::SH_MESSAGE);
214 if messages.is_empty() {
215 messages = self.g.objects(s, vocab::SH_MESSAGE);
216 }
217 let constraint = SparqlConstraint {
218 kind,
219 query,
220 path: path.clone(),
221 shape,
222 messages,
223 extra_bindings: Vec::new(),
224 bind_value_to_this: false,
225 };
226 conjuncts.push(self.arena.insert(Shape::Sparql(constraint)));
227 }
228 }
229
230 for expr_term in self.g.objects(s, vocab::SH_EXPRESSION) {
234 if let Some(expr) = self.parse_node_expr(expr_term, s) {
235 if node_expr_has_function(&expr) {
236 self.diag(
240 DiagLevel::Unsupported,
241 "sh:expression with a function call is not yet evaluated",
242 s,
243 );
244 } else {
245 conjuncts.push(self.arena.insert(Shape::Expression(expr)));
246 }
247 }
248 }
249
250 let body = if conjuncts.is_empty() {
251 self.arena.top()
252 } else if conjuncts.len() == 1 {
253 if conjuncts[0] == id {
254 self.arena.top()
255 } else {
256 conjuncts[0]
257 }
258 } else {
259 self.arena.insert(Shape::And(conjuncts))
260 };
261 self.arena.set(
262 id,
263 Shape::Annotated {
264 severity: self.severity(s),
265 messages: self.messages(s),
266 shape: body,
267 },
268 );
269 id
270 }
271
272 fn severity(&self, shape: &NamedOrBlankNode) -> Severity {
273 match self.g.object(shape, vocab::SH_SEVERITY) {
274 Some(Term::NamedNode(value)) => Severity::from_named_node(value),
275 _ => Severity::Violation,
276 }
277 }
278
279 fn messages(&self, shape: &NamedOrBlankNode) -> std::sync::Arc<[Term]> {
281 self.g.objects(shape, vocab::SH_MESSAGE).into()
282 }
283
284 fn collect_value_constraints(&mut self, s: &NamedOrBlankNode) -> Vec<ShapeId> {
285 let mut value: Vec<ShapeId> = Vec::new();
286
287 for c in self.g.objects(s, vocab::SH_CLASS) {
289 let tn = self.arena.insert(Shape::TestConst(c));
290 let cc = self.arena.count(class_path(), Some(1), None, tn);
291 value.push(cc);
292 }
293
294 for d in self.g.objects(s, vocab::SH_DATATYPE) {
296 if let Term::NamedNode(n) = d {
297 let id = self.arena.insert(Shape::TestType(ValueType::Datatype(n)));
298 value.push(id);
299 }
300 }
301
302 for k in self.g.objects(s, vocab::SH_NODE_KIND) {
304 if let Some(set) = map_node_kind(&k) {
305 let id = self.arena.insert(Shape::TestKind(set));
306 value.push(id);
307 } else {
308 self.diag(DiagLevel::Warning, "unrecognized sh:nodeKind value", s);
309 }
310 }
311
312 let lo = self
314 .lit(s, vocab::SH_MIN_INCLUSIVE)
315 .map(|value| Bound {
316 value,
317 inclusive: true,
318 })
319 .or_else(|| {
320 self.lit(s, vocab::SH_MIN_EXCLUSIVE).map(|value| Bound {
321 value,
322 inclusive: false,
323 })
324 });
325 let hi = self
326 .lit(s, vocab::SH_MAX_INCLUSIVE)
327 .map(|value| Bound {
328 value,
329 inclusive: true,
330 })
331 .or_else(|| {
332 self.lit(s, vocab::SH_MAX_EXCLUSIVE).map(|value| Bound {
333 value,
334 inclusive: false,
335 })
336 });
337 if lo.is_some() || hi.is_some() {
338 let id = self
339 .arena
340 .insert(Shape::TestType(ValueType::NumericRange { lo, hi }));
341 value.push(id);
342 }
343
344 let min_len = self.int(s, vocab::SH_MIN_LENGTH);
346 let max_len = self.int(s, vocab::SH_MAX_LENGTH);
347 if min_len.is_some() || max_len.is_some() {
348 let id = self.arena.insert(Shape::TestType(ValueType::Length {
349 min: min_len,
350 max: max_len,
351 }));
352 value.push(id);
353 }
354
355 let flags = self
357 .lit(s, vocab::SH_FLAGS)
358 .map(|l| l.value().to_string())
359 .unwrap_or_default();
360 for pat in self.g.objects(s, vocab::SH_PATTERN) {
361 if let Term::Literal(l) = pat {
362 let id = self.arena.insert(Shape::TestType(ValueType::Pattern {
363 regex: l.value().to_string(),
364 flags: flags.clone(),
365 }));
366 value.push(id);
367 }
368 }
369
370 for li in self.g.objects(s, vocab::SH_LANGUAGE_IN) {
372 let langs: Vec<String> = self
373 .g
374 .read_list(&li)
375 .into_iter()
376 .filter_map(|m| match m {
377 Term::Literal(l) => Some(l.value().to_string()),
378 _ => None,
379 })
380 .collect();
381 let id = self.arena.insert(Shape::TestType(ValueType::LangIn(langs)));
382 value.push(id);
383 }
384
385 for inl in self.g.objects(s, vocab::SH_IN) {
387 let alts: Vec<ShapeId> = self
388 .g
389 .read_list(&inl)
390 .into_iter()
391 .map(|m| self.arena.insert(Shape::TestConst(m)))
392 .collect();
393 let or = self.arena.or(alts);
394 value.push(or);
395 }
396
397 for n in self.g.objects(s, vocab::SH_NODE) {
399 if let Some(nn) = term_to_node(&n) {
400 let id = self.lower_shape(&nn);
401 value.push(id);
402 }
403 }
404
405 for prop in self.g.objects(s, vocab::SH_PROPERTY) {
409 if let Some(pn) = term_to_node(&prop) {
410 let id = self.lower_shape(&pn);
411 value.push(id);
412 }
413 }
414
415 for n in self.g.objects(s, vocab::SH_NOT) {
417 if let Some(nn) = term_to_node(&n) {
418 let id = self.lower_shape(&nn);
419 let neg = self.arena.not(id);
420 value.push(neg);
421 }
422 }
423
424 for l in self.g.objects(s, vocab::SH_AND) {
426 let ids = self.lower_shape_list(&l);
427 let a = self.arena.and(ids);
428 value.push(a);
429 }
430 for l in self.g.objects(s, vocab::SH_OR) {
431 let ids = self.lower_shape_list(&l);
432 let o = self.arena.or(ids);
433 value.push(o);
434 }
435 for l in self.g.objects(s, vocab::SH_XONE) {
436 let ids = self.lower_shape_list(&l);
437 let x = self.arena.xone(ids);
438 value.push(x);
439 }
440
441 value
442 }
443
444 fn collect_path_constraints(
448 &mut self,
449 s: &NamedOrBlankNode,
450 path: Option<&Path>,
451 conjuncts: &mut Vec<ShapeId>,
452 ) {
453 let need_path = |me: &mut Self, what: &str| {
454 me.diag(DiagLevel::Warning, format!("{what} ignored: no sh:path"), s);
455 };
456
457 let min_count = self.int(s, vocab::SH_MIN_COUNT);
458 let max_count = self.int(s, vocab::SH_MAX_COUNT);
459 if min_count.is_some() || max_count.is_some() {
460 match path {
461 Some(p) => {
462 let top = self.arena.top();
463 let c = self.arena.count(p.clone(), min_count, max_count, top);
464 conjuncts.push(c);
465 }
466 None => need_path(self, "sh:minCount/sh:maxCount"),
467 }
468 }
469
470 for v in self.g.objects(s, vocab::SH_HAS_VALUE) {
472 match path {
473 Some(p) => {
474 let tc = self.arena.insert(Shape::TestConst(v));
475 let c = self.arena.count(p.clone(), Some(1), None, tc);
476 conjuncts.push(c);
477 }
478 None => {
479 let tc = self.arena.insert(Shape::TestConst(v));
480 conjuncts.push(tc);
481 }
482 }
483 }
484
485 for q in self.g.objects(s, vocab::SH_QUALIFIED_VALUE_SHAPE) {
487 if let Some(qn) = term_to_node(&q) {
488 let qmin = self.int(s, vocab::SH_QUALIFIED_MIN_COUNT);
489 let qmax = self.int(s, vocab::SH_QUALIFIED_MAX_COUNT);
490 match path {
491 Some(p) => {
492 let mut qualifiers = vec![self.lower_shape(&qn)];
493 if self.bool_prop(s, vocab::SH_QUALIFIED_VALUE_SHAPES_DISJOINT) {
494 for sibling in self.sibling_qualified_shapes(s, &qn) {
495 let sibling = self.lower_shape(&sibling);
496 qualifiers.push(self.arena.not(sibling));
497 }
498 }
499 let qualifier = self.arena.and(qualifiers);
500 let c = self.arena.count(p.clone(), qmin, qmax, qualifier);
501 conjuncts.push(c);
502 }
503 None => need_path(self, "sh:qualifiedValueShape"),
504 }
505 }
506 }
507
508 let pairs = [
510 (vocab::SH_EQUALS, "equals"),
511 (vocab::SH_DISJOINT, "disjoint"),
512 (vocab::SH_LESS_THAN, "lessThan"),
513 (vocab::SH_LESS_THAN_OR_EQUALS, "lessThanOrEquals"),
514 ];
515 for (pred, name) in pairs {
516 for other in self.g.objects(s, pred) {
517 let Term::NamedNode(op) = other else { continue };
518 match path {
519 Some(p) => {
520 let shape = match name {
521 "equals" => Shape::Eq(p.clone(), op),
522 "disjoint" => Shape::Disj(p.clone(), op),
523 "lessThan" => Shape::Lt(p.clone(), op),
524 _ => Shape::Le(p.clone(), op),
525 };
526 let c = self.arena.insert(shape);
527 conjuncts.push(c);
528 }
529 None if matches!(name, "equals" | "disjoint") => {
530 let shape = if name == "equals" {
531 Shape::Eq(Path::Id, op)
532 } else {
533 Shape::Disj(Path::Id, op)
534 };
535 let c = self.arena.insert(shape);
536 conjuncts.push(c);
537 }
538 None => need_path(self, &format!("sh:{name}")),
539 }
540 }
541 }
542
543 if self.bool_prop(s, vocab::SH_UNIQUE_LANG) {
545 match path {
546 Some(p) => {
547 let c = self.arena.insert(Shape::UniqueLang(p.clone()));
548 conjuncts.push(c);
549 }
550 None => need_path(self, "sh:uniqueLang"),
551 }
552 }
553 }
554
555 fn target_selectors(&mut self, s: &NamedOrBlankNode) -> Vec<Selector> {
557 let mut sels = Vec::new();
558
559 for c in self.g.objects(s, vocab::SH_TARGET_NODE) {
560 sels.push(Selector::IsConst(c));
561 }
562 for c in self.g.objects(s, vocab::SH_TARGET_CLASS) {
563 sels.push(self.class_selector(c));
564 }
565 for p in self.g.objects(s, vocab::SH_TARGET_SUBJECTS_OF) {
566 if let Term::NamedNode(n) = p {
567 sels.push(Selector::HasOut(n));
568 }
569 }
570 for p in self.g.objects(s, vocab::SH_TARGET_OBJECTS_OF) {
571 if let Term::NamedNode(n) = p {
572 sels.push(Selector::HasIn(n));
573 }
574 }
575
576 if (self.g.is_instance_of(s, vocab::RDFS_CLASS)
578 || self.g.is_instance_of(s, vocab::OWL_CLASS))
579 && let NamedOrBlankNode::NamedNode(n) = s
580 {
581 sels.push(self.class_selector(Term::NamedNode(n.clone())));
582 }
583
584 for target_term in self.g.objects(s, vocab::SH_TARGET) {
585 let Some(target_node) = term_to_node(&target_term) else {
586 self.diag(DiagLevel::Error, "sh:target must reference a resource", s);
587 continue;
588 };
589 match self.g.object(&target_node, vocab::SH_SELECT) {
590 Some(Term::Literal(query)) => {
591 if let Some(query) =
592 self.canonical_sparql(&target_node, query.value(), ExpectedQuery::Select)
593 {
594 sels.push(Selector::Sparql(SparqlTarget { query }));
595 }
596 }
597 _ => self.diag(
598 DiagLevel::Unsupported,
599 "custom sh:target without sh:select is not yet lowered",
600 &target_node,
601 ),
602 }
603 }
604
605 sels
606 }
607
608 fn lower_custom_components(&mut self, shapes: &[NamedOrBlankNode]) {
618 let components = self.collect_component_defs();
619 if components.is_empty() {
620 return;
621 }
622 for s in shapes {
623 let Some(&shape_id) = self.cache.get(s) else {
624 continue;
625 };
626 let is_property_shape = self.g.object(s, vocab::SH_PATH).is_some();
627 let path = if is_property_shape {
628 self.parse_shape_path(s)
629 } else {
630 None
631 };
632 let shape_term = match s {
633 NamedOrBlankNode::NamedNode(n) => Some(Term::NamedNode(n.clone())),
634 NamedOrBlankNode::BlankNode(b) => Some(Term::BlankNode(b.clone())),
635 };
636
637 for component in &components {
638 let mut extra_bindings: Vec<(String, Term)> = Vec::new();
640 let mut activated = true;
641 for p in &component.params {
642 if let Some(value) = self.g.object(s, p.path.as_ref()) {
643 extra_bindings.push((p.var.clone(), value));
644 } else if !p.optional {
645 activated = false;
646 break;
647 }
648 }
649 if !activated {
650 continue;
651 }
652
653 let validator = if is_property_shape {
655 component
656 .property_validator
657 .as_ref()
658 .or(component.generic_validator.as_ref())
659 } else {
660 component
661 .node_validator
662 .as_ref()
663 .or(component.generic_validator.as_ref())
664 };
665 let Some(validator) = validator else { continue };
666
667 let constraint = SparqlConstraint {
668 kind: validator.kind,
669 query: validator.query.clone(),
670 path: path.clone(),
671 shape: shape_term.clone(),
672 messages: validator.messages.clone(),
673 extra_bindings,
674 bind_value_to_this: !is_property_shape
677 && validator.kind == SparqlQueryKind::Ask,
678 };
679 let raw_id = self.arena.insert(Shape::Sparql(constraint));
680 let sparql_id = if validator.kind == SparqlQueryKind::Ask {
686 self.arena.not(raw_id)
687 } else {
688 raw_id
689 };
690
691 let (severity, messages, inner) = match self.arena.get(shape_id) {
693 Shape::Annotated {
694 severity,
695 messages,
696 shape: inner,
697 } => (severity.clone(), messages.clone(), *inner),
698 _ => continue, };
700 let combined = self.arena.and(vec![inner, sparql_id]);
701 self.arena.set(
702 shape_id,
703 Shape::Annotated {
704 severity,
705 messages,
706 shape: combined,
707 },
708 );
709 }
710 }
711 }
712
713 fn collect_component_defs(&mut self) -> Vec<ComponentDef> {
717 let mut out: Vec<ComponentDef> = Vec::new();
718 let mut seen = HashSet::new();
719 for triple in self.g.graph.triples_for_predicate(vocab::SH_PARAMETER) {
720 let subject = triple.subject.into_owned();
721 if !seen.insert(subject.clone()) {
722 continue;
723 }
724 let NamedOrBlankNode::NamedNode(iri) = &subject else {
725 continue;
726 };
727 if vocab::NATIVE_CONSTRAINT_COMPONENTS.contains(&iri.as_ref()) {
728 continue; }
730 let node_validator = self
731 .g
732 .object(&subject, vocab::SH_NODE_VALIDATOR)
733 .and_then(|v| self.parse_validator_def(&v));
734 let property_validator = self
735 .g
736 .object(&subject, vocab::SH_PROPERTY_VALIDATOR)
737 .and_then(|v| self.parse_validator_def(&v));
738 let generic_validator = self
739 .g
740 .object(&subject, vocab::SH_VALIDATOR)
741 .and_then(|v| self.parse_validator_def(&v));
742 if node_validator.is_none()
743 && property_validator.is_none()
744 && generic_validator.is_none()
745 {
746 continue; }
748 let mut params: Vec<ParamDef> = Vec::new();
749 for p in self.g.objects(&subject, vocab::SH_PARAMETER) {
750 let Some(pn) = term_to_node(&p) else { continue };
751 let Some(Term::NamedNode(path)) = self.g.object(&pn, vocab::SH_PATH) else {
752 continue;
753 };
754 let var = local_name(path.as_str()).to_string();
755 let optional = matches!(
756 self.g.object(&pn, vocab::SH_OPTIONAL),
757 Some(Term::Literal(ref l)) if l.value() == "true"
758 );
759 params.push(ParamDef {
760 path,
761 var,
762 optional,
763 });
764 }
765 if params.is_empty() {
766 continue;
767 }
768 out.push(ComponentDef {
769 iri: iri.clone(),
770 params,
771 node_validator,
772 property_validator,
773 generic_validator,
774 });
775 }
776 out.sort_by(|a, b| a.iri.as_str().cmp(b.iri.as_str()));
777 out
778 }
779
780 fn parse_validator_def(&mut self, node: &Term) -> Option<ValidatorDef> {
784 let node = term_to_node(node)?;
785 let (kind, raw) = if let Some(Term::Literal(q)) = self.g.object(&node, vocab::SH_ASK) {
786 (SparqlQueryKind::Ask, q.value().to_string())
787 } else if let Some(Term::Literal(q)) = self.g.object(&node, vocab::SH_SELECT) {
788 (SparqlQueryKind::Select, q.value().to_string())
789 } else {
790 return None; };
792 let canonical = match canonical_sparql_query(self.g, &node, &raw) {
793 Ok((_, c)) => c,
794 Err(msg) => {
795 self.diag(
796 DiagLevel::Error,
797 format!("invalid SPARQL in validator: {msg}"),
798 &node,
799 );
800 return None;
801 }
802 };
803 let messages = self.g.objects(&node, vocab::SH_MESSAGE);
804 Some(ValidatorDef {
805 kind,
806 query: canonical,
807 messages,
808 })
809 }
810
811 fn parse_rules(&mut self, s: &NamedOrBlankNode, selectors: &[Selector]) {
814 for rule_term in self.g.objects(s, vocab::SH_RULE) {
815 let Some(rn) = term_to_node(&rule_term) else {
816 continue;
817 };
818 let Some(head) = self.parse_rule_head(&rn) else {
819 continue;
820 };
821
822 let conditions: Vec<ShapeId> = self
823 .g
824 .objects(&rn, vocab::SH_CONDITION)
825 .iter()
826 .filter_map(term_to_node)
827 .map(|c| self.lower_shape(&c))
828 .collect();
829 let order = self.order(&rn);
830 let deactivated = self.bool_prop(&rn, vocab::SH_DEACTIVATED);
831
832 for sel in selectors {
833 self.rules.push(Rule {
834 selector: sel.clone(),
835 conditions: conditions.clone(),
836 head: head.clone(),
837 order,
838 deactivated,
839 });
840 }
841 }
842 }
843
844 fn sibling_qualified_shapes(
847 &self,
848 shape: &NamedOrBlankNode,
849 qualifier: &NamedOrBlankNode,
850 ) -> Vec<NamedOrBlankNode> {
851 let mut siblings = HashSet::new();
852 for triple in self.g.graph.triples_for_predicate(vocab::SH_PROPERTY) {
853 if term_to_node(&triple.object.into_owned()).as_ref() != Some(shape) {
854 continue;
855 }
856 let parent = triple.subject.into_owned();
857 for property in self.g.objects(&parent, vocab::SH_PROPERTY) {
858 let Some(property) = term_to_node(&property) else {
859 continue;
860 };
861 for sibling in self.g.objects(&property, vocab::SH_QUALIFIED_VALUE_SHAPE) {
862 if let Some(sibling) = term_to_node(&sibling) {
863 siblings.insert(sibling);
864 }
865 }
866 }
867 }
868 siblings.remove(qualifier);
869 let mut siblings: Vec<_> = siblings.into_iter().collect();
870 siblings.sort_by_key(|node| node.to_string());
871 siblings
872 }
873
874 fn parse_rule_head(&mut self, rn: &NamedOrBlankNode) -> Option<RuleHead> {
875 if let Some(Term::Literal(q)) = self.g.object(rn, vocab::SH_CONSTRUCT) {
878 let query = self.canonical_sparql(rn, q.value(), ExpectedQuery::Construct)?;
879 return Some(RuleHead::Sparql(SparqlConstruct { query }));
880 }
881 let (subj, pred, obj) = (
883 self.g.object(rn, vocab::SH_SUBJECT),
884 self.g.object(rn, vocab::SH_PREDICATE),
885 self.g.object(rn, vocab::SH_OBJECT),
886 );
887 if subj.is_none() && pred.is_none() && obj.is_none() {
888 self.diag(DiagLevel::Unsupported, "unrecognized sh:rule head", rn);
889 return None;
890 }
891 let (Some(subj), Some(pred), Some(obj)) = (subj, pred, obj) else {
892 self.diag(
893 DiagLevel::Error,
894 "sh:TripleRule missing subject/predicate/object",
895 rn,
896 );
897 return None;
898 };
899 Some(RuleHead::Triple {
900 subject: self.parse_node_expr(subj, rn)?,
901 predicate: self.parse_node_expr(pred, rn)?,
902 object: self.parse_node_expr(obj, rn)?,
903 })
904 }
905
906 fn parse_node_expr(&mut self, term: Term, owner: &NamedOrBlankNode) -> Option<NodeExpr> {
910 match &term {
911 Term::NamedNode(n) if n.as_ref() == vocab::SH_THIS => Some(NodeExpr::This),
912 Term::NamedNode(_) | Term::Literal(_) => Some(NodeExpr::Constant(term)),
913 Term::BlankNode(_) => {
914 let node = term_to_node(&term).expect("blank node");
915 if let Some(path_term) = self.g.object(&node, vocab::SH_PATH) {
916 match parse_path(self.g, &path_term) {
917 Ok(path) => Some(NodeExpr::Path(path)),
918 Err(e) => {
919 self.diag(
920 DiagLevel::Error,
921 format!("invalid node-expression path: {e}"),
922 owner,
923 );
924 None
925 }
926 }
927 } else if self.g.object(&node, vocab::SH_FILTER_SHAPE).is_some() {
928 self.parse_filter_expr(&node, owner)
929 } else if let Some(list) = self.g.object(&node, vocab::SH_INTERSECTION) {
930 self.parse_set_expr(&list, owner)
931 .map(NodeExpr::Intersection)
932 } else if let Some(list) = self.g.object(&node, vocab::SH_UNION) {
933 self.parse_set_expr(&list, owner).map(NodeExpr::Union)
934 } else if let Some(expr) = self.try_function_call(&node, owner) {
935 Some(expr)
936 } else {
937 self.diag(
938 DiagLevel::Unsupported,
939 "complex node expression not yet lowered",
940 owner,
941 );
942 None
943 }
944 }
945 }
946 }
947
948 fn parse_filter_expr(
951 &mut self,
952 node: &NamedOrBlankNode,
953 owner: &NamedOrBlankNode,
954 ) -> Option<NodeExpr> {
955 let Some(shape_term) = self.g.object(node, vocab::SH_FILTER_SHAPE) else {
956 self.diag(DiagLevel::Error, "sh:filterShape missing a value", owner);
957 return None;
958 };
959 let Some(shape_node) = term_to_node(&shape_term) else {
960 self.diag(
961 DiagLevel::Error,
962 "sh:filterShape must reference a shape",
963 owner,
964 );
965 return None;
966 };
967 let Some(nodes_term) = self.g.object(node, vocab::SH_NODES) else {
968 self.diag(
969 DiagLevel::Error,
970 "filter expression missing sh:nodes",
971 owner,
972 );
973 return None;
974 };
975 let input = self.parse_node_expr(nodes_term, owner)?;
976 let shape = self.lower_shape(&shape_node);
977 Some(NodeExpr::Filter {
978 input: Box::new(input),
979 shape,
980 })
981 }
982
983 fn parse_set_expr(
987 &mut self,
988 list_head: &Term,
989 owner: &NamedOrBlankNode,
990 ) -> Option<Vec<NodeExpr>> {
991 let members = self.g.read_list(list_head);
992 if members.is_empty() {
993 self.diag(
994 DiagLevel::Error,
995 "sh:intersection/sh:union expects a non-empty list",
996 owner,
997 );
998 return None;
999 }
1000 let n = members.len();
1001 let exprs: Vec<NodeExpr> = members
1002 .into_iter()
1003 .filter_map(|t| self.parse_node_expr(t, owner))
1004 .collect();
1005 if exprs.len() != n {
1006 return None;
1007 }
1008 Some(exprs)
1009 }
1010
1011 fn try_function_call(
1016 &mut self,
1017 node: &NamedOrBlankNode,
1018 owner: &NamedOrBlankNode,
1019 ) -> Option<NodeExpr> {
1020 let func_preds: Vec<(NamedNode, Term)> = self
1021 .g
1022 .graph
1023 .triples_for_subject(node)
1024 .map(|t| (t.predicate.into_owned(), t.object.into_owned()))
1025 .filter(|(p, _)| {
1026 let s = p.as_str();
1027 !s.starts_with(vocab::SH)
1028 && !s.starts_with(vocab::RDF)
1029 && !s.starts_with(vocab::RDFS)
1030 && !s.starts_with(vocab::OWL)
1031 })
1032 .collect();
1033
1034 if func_preds.len() != 1 {
1035 return None;
1036 }
1037 let (func_iri, list_head) = func_preds.into_iter().next().unwrap();
1038 let arg_terms = self.g.read_list(&list_head);
1039 let n = arg_terms.len();
1040 let args: Vec<NodeExpr> = arg_terms
1041 .into_iter()
1042 .filter_map(|t| self.parse_node_expr(t, owner))
1043 .collect();
1044 if args.len() != n {
1045 return None;
1046 }
1047 Some(NodeExpr::Function {
1048 iri: func_iri,
1049 args,
1050 })
1051 }
1052
1053 fn order(&self, s: &NamedOrBlankNode) -> Option<i64> {
1054 match self.g.object(s, vocab::SH_ORDER) {
1055 Some(Term::Literal(l)) => l.value().parse().ok(),
1056 _ => None,
1057 }
1058 }
1059
1060 fn class_selector(&mut self, class: Term) -> Selector {
1062 let tn = self.arena.insert(Shape::TestConst(class));
1063 Selector::HasPath(class_path(), tn)
1064 }
1065
1066 fn lower_shape_list(&mut self, list_head: &Term) -> Vec<ShapeId> {
1067 self.g
1068 .read_list(list_head)
1069 .into_iter()
1070 .filter_map(|m| term_to_node(&m))
1071 .map(|n| self.lower_shape(&n))
1072 .collect()
1073 }
1074
1075 fn parse_shape_path(&mut self, s: &NamedOrBlankNode) -> Option<Path> {
1076 let term = self.g.object(s, vocab::SH_PATH)?;
1077 match parse_path(self.g, &term) {
1078 Ok(p) => Some(p),
1079 Err(e) => {
1080 self.diag(DiagLevel::Error, format!("invalid sh:path: {e}"), s);
1081 None
1082 }
1083 }
1084 }
1085
1086 fn closed_allowed(&self, s: &NamedOrBlankNode) -> BTreeSet<oxrdf::NamedNode> {
1087 let mut q = BTreeSet::new();
1088 for prop in self.g.objects(s, vocab::SH_PROPERTY) {
1089 if let Some(pn) = term_to_node(&prop)
1090 && let Some(Term::NamedNode(n)) = self.g.object(&pn, vocab::SH_PATH)
1091 {
1092 q.insert(n);
1093 }
1094 }
1095 for ip in self.g.objects(s, vocab::SH_IGNORED_PROPERTIES) {
1096 for m in self.g.read_list(&ip) {
1097 if let Term::NamedNode(n) = m {
1098 q.insert(n);
1099 }
1100 }
1101 }
1102 q
1103 }
1104
1105 fn bool_prop(&self, s: &NamedOrBlankNode, pred: oxrdf::NamedNodeRef) -> bool {
1106 matches!(self.g.object(s, pred), Some(Term::Literal(l)) if l.value() == "true")
1107 }
1108
1109 fn int(&self, s: &NamedOrBlankNode, pred: oxrdf::NamedNodeRef) -> Option<u64> {
1110 match self.g.object(s, pred) {
1111 Some(Term::Literal(l)) => l.value().parse().ok(),
1112 _ => None,
1113 }
1114 }
1115
1116 fn lit(&self, s: &NamedOrBlankNode, pred: oxrdf::NamedNodeRef) -> Option<Literal> {
1117 match self.g.object(s, pred) {
1118 Some(Term::Literal(l)) => Some(l),
1119 _ => None,
1120 }
1121 }
1122
1123 fn canonical_sparql(
1127 &mut self,
1128 owner: &NamedOrBlankNode,
1129 raw: &str,
1130 expected: ExpectedQuery,
1131 ) -> Option<String> {
1132 let (query, canonical) = match canonical_sparql_query(self.g, owner, raw) {
1133 Ok(result) => result,
1134 Err(message) => {
1135 self.diag(DiagLevel::Error, message, owner);
1136 return None;
1137 }
1138 };
1139 let actual = match &query {
1140 Query::Select { .. } => ExpectedQuery::Select,
1141 Query::Ask { .. } => ExpectedQuery::Ask,
1142 Query::Construct { .. } => ExpectedQuery::Construct,
1143 Query::Describe { .. } => ExpectedQuery::Describe,
1144 };
1145 if actual != expected {
1146 self.diag(
1147 DiagLevel::Error,
1148 format!("expected SPARQL {expected}, found {actual}"),
1149 owner,
1150 );
1151 return None;
1152 }
1153 Some(canonical)
1154 }
1155}
1156
1157pub fn canonical_sparql_query(
1169 g: &Loaded,
1170 owner: &NamedOrBlankNode,
1171 raw: &str,
1172) -> Result<(Query, String), String> {
1173 let mut parser = SparqlParser::new();
1174 if let Some(base) = &g.base {
1175 parser = parser
1176 .with_base_iri(base)
1177 .map_err(|e| format!("invalid SPARQL base IRI: {e}"))?;
1178 }
1179 for (prefix, namespace) in &g.prefixes {
1180 parser = parser
1181 .with_prefix(prefix, namespace)
1182 .map_err(|e| format!("invalid SPARQL prefix declaration {prefix}: {e}"))?;
1183 }
1184 let mut prefix_sources: Vec<NamedOrBlankNode> = g
1185 .objects(owner, vocab::SH_PREFIXES)
1186 .iter()
1187 .filter_map(term_to_node)
1188 .collect();
1189 let mut seen_sources = HashSet::new();
1190 while let Some(source) = prefix_sources.pop() {
1191 if !seen_sources.insert(source.clone()) {
1192 continue;
1193 }
1194 prefix_sources.extend(
1195 g.objects(&source, vocab::OWL_IMPORTS)
1196 .iter()
1197 .filter_map(term_to_node),
1198 );
1199 for declaration_term in g.objects(&source, vocab::SH_DECLARE) {
1200 let Some(declaration) = term_to_node(&declaration_term) else {
1201 continue;
1202 };
1203 let (Some(Term::Literal(prefix)), Some(Term::Literal(namespace))) = (
1204 g.object(&declaration, vocab::SH_PREFIX),
1205 g.object(&declaration, vocab::SH_NAMESPACE),
1206 ) else {
1207 continue;
1208 };
1209 parser = parser
1210 .with_prefix(prefix.value(), namespace.value())
1211 .map_err(|e| format!("invalid SHACL SPARQL prefix declaration: {e}"))?;
1212 }
1213 }
1214 let query = parser
1215 .parse_query(raw)
1216 .map_err(|e| format!("invalid SPARQL query: {e}"))?;
1217 let canonical = query.to_string();
1218 Ok((query, canonical))
1219}
1220
1221#[derive(Clone, Copy, PartialEq, Eq)]
1222enum ExpectedQuery {
1223 Select,
1224 Ask,
1225 Construct,
1226 Describe,
1227}
1228
1229impl std::fmt::Display for ExpectedQuery {
1230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1231 f.write_str(match self {
1232 Self::Select => "SELECT",
1233 Self::Ask => "ASK",
1234 Self::Construct => "CONSTRUCT",
1235 Self::Describe => "DESCRIBE",
1236 })
1237 }
1238}
1239
1240fn class_path() -> Path {
1241 Path::seq(vec![
1242 Path::Pred(vocab::rdf_type()),
1243 Path::star(Path::Pred(vocab::rdfs_subclassof())),
1244 ])
1245}
1246
1247fn node_expr_has_function(e: &NodeExpr) -> bool {
1252 match e {
1253 NodeExpr::Function { .. } => true,
1254 NodeExpr::Filter { input, .. } => node_expr_has_function(input),
1255 NodeExpr::Intersection(es) | NodeExpr::Union(es) => es.iter().any(node_expr_has_function),
1256 NodeExpr::This | NodeExpr::Constant(_) | NodeExpr::Path(_) => false,
1257 }
1258}
1259
1260fn map_node_kind(term: &Term) -> Option<NodeKindSet> {
1261 let Term::NamedNode(n) = term else {
1262 return None;
1263 };
1264 let r = n.as_ref();
1265 Some(if r == vocab::SH_IRI {
1266 NodeKindSet::IRI
1267 } else if r == vocab::SH_BLANK_NODE {
1268 NodeKindSet::BLANK_NODE
1269 } else if r == vocab::SH_LITERAL {
1270 NodeKindSet::LITERAL
1271 } else if r == vocab::SH_BLANK_NODE_OR_IRI {
1272 NodeKindSet::BLANK_NODE_OR_IRI
1273 } else if r == vocab::SH_BLANK_NODE_OR_LITERAL {
1274 NodeKindSet::BLANK_NODE_OR_LITERAL
1275 } else if r == vocab::SH_IRI_OR_LITERAL {
1276 NodeKindSet::IRI_OR_LITERAL
1277 } else {
1278 return None;
1279 })
1280}