use std::collections::HashSet;
use std::sync::OnceLock;
use oxrdf::vocab::rdf;
use oxrdf::{Graph, NamedNode, NamedOrBlankNode, Term, TermRef, Triple};
use vertep_rdf::io::{RdfFormat, read_graph};
use vertep_rdf::shacl::{Shapes, ValidationReport};
use vertep_store::Entry;
pub use vertep_rdf::RUNTIME_ONTOLOGY_TTL;
pub const RUNTIME_SHAPES_TTL: &str = include_str!("../shapes/vt-runtime-shapes.ttl");
const RDFS_SUBCLASS: &str = "http://www.w3.org/2000/01/rdf-schema#subClassOf";
const RDFS_SUBPROPERTY: &str = "http://www.w3.org/2000/01/rdf-schema#subPropertyOf";
pub fn runtime_ontology() -> Graph {
read_graph(RUNTIME_ONTOLOGY_TTL.as_bytes(), RdfFormat::Turtle).unwrap_or_default()
}
pub fn runtime_shapes() -> &'static Shapes {
static SHAPES: OnceLock<Shapes> = OnceLock::new();
SHAPES.get_or_init(|| {
let g = read_graph(RUNTIME_SHAPES_TTL.as_bytes(), RdfFormat::Turtle)
.unwrap_or_else(|e| panic!("the bundled runtime shapes parse: {e:?}"));
Shapes::from_graph(&g)
.unwrap_or_else(|e| panic!("the bundled runtime shapes compile: {e:?}"))
})
}
pub fn validate(data: &Graph) -> ValidationReport {
runtime_shapes().validate(data)
}
pub fn validate_entry(entry: &Entry) -> ValidationReport {
validate(entry.triples())
}
pub fn rdfs_closure(data: &Graph, ontology: &Graph) -> Graph {
let sub = |p: &str| -> Vec<(NamedNode, NamedNode)> {
let p = NamedNode::new_unchecked(p);
ontology
.triples_for_predicate(&p)
.filter_map(|t| match (t.subject, t.object) {
(oxrdf::NamedOrBlankNodeRef::NamedNode(a), TermRef::NamedNode(b)) => {
Some((a.into_owned(), b.into_owned()))
}
_ => None,
})
.collect()
};
let closure = |pairs: Vec<(NamedNode, NamedNode)>| {
let mut set: HashSet<(NamedNode, NamedNode)> = pairs.into_iter().collect();
loop {
let mut added = Vec::new();
for (a, b) in &set {
for (c, d) in &set {
if b == c && !set.contains(&(a.clone(), d.clone())) {
added.push((a.clone(), d.clone()));
}
}
}
if added.is_empty() {
break set;
}
set.extend(added);
}
};
let classes = closure(sub(RDFS_SUBCLASS));
let props = closure(sub(RDFS_SUBPROPERTY));
let mut out = data.clone();
let mut extra: Vec<Triple> = Vec::new();
for t in data.iter() {
let s: NamedOrBlankNode = t.subject.into_owned();
let p = t.predicate.into_owned();
let o: Term = t.object.into_owned();
for (a, b) in &props {
if *a == p {
extra.push(Triple::new(s.clone(), b.clone(), o.clone()));
}
}
}
for t in &extra {
out.insert(t);
}
let typed: Vec<(NamedOrBlankNode, NamedNode)> = out
.triples_for_predicate(rdf::TYPE)
.filter_map(|t| match t.object {
TermRef::NamedNode(c) => Some((t.subject.into_owned(), c.into_owned())),
_ => None,
})
.collect();
for (s, c) in typed {
for (a, b) in &classes {
if *a == c {
out.insert(&Triple::new(s.clone(), rdf::TYPE, b.clone()));
}
}
}
out
}