vertep_runtime/
ontology.rs1use std::collections::HashSet;
10use std::sync::OnceLock;
11
12use oxrdf::vocab::rdf;
13use oxrdf::{Graph, NamedNode, NamedOrBlankNode, Term, TermRef, Triple};
14use vertep_rdf::io::{RdfFormat, read_graph};
15use vertep_rdf::shacl::{Shapes, ValidationReport};
16use vertep_store::Entry;
17
18pub use vertep_rdf::RUNTIME_ONTOLOGY_TTL;
20
21pub const RUNTIME_SHAPES_TTL: &str = include_str!("../shapes/vt-runtime-shapes.ttl");
23
24const RDFS_SUBCLASS: &str = "http://www.w3.org/2000/01/rdf-schema#subClassOf";
25const RDFS_SUBPROPERTY: &str = "http://www.w3.org/2000/01/rdf-schema#subPropertyOf";
26
27pub fn runtime_ontology() -> Graph {
33 read_graph(RUNTIME_ONTOLOGY_TTL.as_bytes(), RdfFormat::Turtle).unwrap_or_default()
34}
35
36pub fn runtime_shapes() -> &'static Shapes {
38 static SHAPES: OnceLock<Shapes> = OnceLock::new();
39 SHAPES.get_or_init(|| {
40 let g = read_graph(RUNTIME_SHAPES_TTL.as_bytes(), RdfFormat::Turtle)
41 .unwrap_or_else(|e| panic!("the bundled runtime shapes parse: {e:?}"));
42 Shapes::from_graph(&g)
43 .unwrap_or_else(|e| panic!("the bundled runtime shapes compile: {e:?}"))
44 })
45}
46
47pub fn validate(data: &Graph) -> ValidationReport {
50 runtime_shapes().validate(data)
51}
52
53pub fn validate_entry(entry: &Entry) -> ValidationReport {
55 validate(entry.triples())
56}
57
58pub fn rdfs_closure(data: &Graph, ontology: &Graph) -> Graph {
61 let sub = |p: &str| -> Vec<(NamedNode, NamedNode)> {
62 let p = NamedNode::new_unchecked(p);
63 ontology
64 .triples_for_predicate(&p)
65 .filter_map(|t| match (t.subject, t.object) {
66 (oxrdf::NamedOrBlankNodeRef::NamedNode(a), TermRef::NamedNode(b)) => {
67 Some((a.into_owned(), b.into_owned()))
68 }
69 _ => None,
70 })
71 .collect()
72 };
73 let closure = |pairs: Vec<(NamedNode, NamedNode)>| {
74 let mut set: HashSet<(NamedNode, NamedNode)> = pairs.into_iter().collect();
75 loop {
76 let mut added = Vec::new();
77 for (a, b) in &set {
78 for (c, d) in &set {
79 if b == c && !set.contains(&(a.clone(), d.clone())) {
80 added.push((a.clone(), d.clone()));
81 }
82 }
83 }
84 if added.is_empty() {
85 break set;
86 }
87 set.extend(added);
88 }
89 };
90 let classes = closure(sub(RDFS_SUBCLASS));
91 let props = closure(sub(RDFS_SUBPROPERTY));
92 let mut out = data.clone();
93 let mut extra: Vec<Triple> = Vec::new();
94 for t in data.iter() {
95 let s: NamedOrBlankNode = t.subject.into_owned();
96 let p = t.predicate.into_owned();
97 let o: Term = t.object.into_owned();
98 for (a, b) in &props {
99 if *a == p {
100 extra.push(Triple::new(s.clone(), b.clone(), o.clone()));
101 }
102 }
103 }
104 for t in &extra {
105 out.insert(t);
106 }
107 let typed: Vec<(NamedOrBlankNode, NamedNode)> = out
108 .triples_for_predicate(rdf::TYPE)
109 .filter_map(|t| match t.object {
110 TermRef::NamedNode(c) => Some((t.subject.into_owned(), c.into_owned())),
111 _ => None,
112 })
113 .collect();
114 for (s, c) in typed {
115 for (a, b) in &classes {
116 if *a == c {
117 out.insert(&Triple::new(s.clone(), rdf::TYPE, b.clone()));
118 }
119 }
120 }
121 out
122}