#![allow(dead_code)]
use rudof_rdf::rdf_core::term::Object;
use rudof_rdf::rdf_core::term::Term as _;
use rudof_rdf::rdf_core::term::Triple as _;
use rudof_rdf::rdf_core::vocabs::{RdfVocab, ShexRVocab};
use rudof_rdf::rdf_core::{NeighsRDF, Rdf};
use rudof_rdf::rdf_impl::OxigraphInMemory;
use serde::Deserialize;
use std::path::{Path, PathBuf};
#[derive(Deserialize)]
struct ManifestFile {
#[serde(rename = "@graph")]
graph: Vec<ManifestGraph>,
}
#[derive(Deserialize)]
struct ManifestGraph {
entries: Vec<ManifestEntry>,
}
#[derive(Deserialize)]
pub struct ManifestEntry {
pub name: String,
pub json: String,
pub ttl: String,
}
pub fn schemas_dir() -> PathBuf {
Path::new(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../shex_testsuite/shexTest/schemas"
))
.to_path_buf()
}
pub fn read_manifest(dir: &Path) -> Vec<ManifestEntry> {
let text = std::fs::read_to_string(dir.join("manifest.jsonld")).expect("reading shexTest schemas manifest");
let manifest: ManifestFile = serde_json::from_str(&text).expect("parsing shexTest schemas manifest");
manifest.graph.into_iter().flat_map(|g| g.entries).collect()
}
pub fn schema_root(graph: &OxigraphInMemory) -> <OxigraphInMemory as Rdf>::Term {
let rdf_type: <OxigraphInMemory as Rdf>::IRI = RdfVocab::rdf_type().into();
let sx_schema: <OxigraphInMemory as Rdf>::Term = ShexRVocab::sx_schema().into();
let mut matches = graph
.triples_with_predicate_object(&rdf_type, &sx_schema)
.expect("querying for sx:Schema node");
let root = matches.next().expect("graph has no sx:Schema node").subj().clone();
matches
.next()
.is_none()
.then_some(())
.expect("graph has more than one sx:Schema node");
root.into()
}
pub fn assert_same_literal(a: <OxigraphInMemory as Rdf>::Term, e: <OxigraphInMemory as Rdf>::Term, entry: &str) {
let a_obj: Object = a
.clone()
.try_into()
.unwrap_or_else(|_| panic!("[{entry}] {a} is not an RDF term"));
let e_obj: Object = e
.clone()
.try_into()
.unwrap_or_else(|_| panic!("[{entry}] {e} is not an RDF term"));
let (Object::Literal(a_lit), Object::Literal(e_lit)) = (a_obj, e_obj) else {
assert_eq!(a.to_string(), e.to_string(), "[{entry}] literal mismatch");
return;
};
let (a_lex, e_lex) = (a_lit.lexical_form(), e_lit.lexical_form());
if a_lex == e_lex && a_lit.datatype().to_string() == e_lit.datatype().to_string() {
return;
}
match (a_lex.trim().parse::<f64>(), e_lex.trim().parse::<f64>()) {
(Ok(an), Ok(en)) => assert!(
(an - en).abs() <= (an.abs().max(en.abs()) * 1e-9).max(1e-9),
"[{entry}] numeric literal value mismatch: {a_lex} ({}) vs {e_lex} ({})",
a_lit.datatype(),
e_lit.datatype()
),
_ if a_lex.eq_ignore_ascii_case(&e_lex) => {},
_ => {
assert_eq!(
a_lit.datatype().to_string(),
e_lit.datatype().to_string(),
"[{entry}] literal datatype mismatch: {a} vs {e}"
);
assert_eq!(a_lex, e_lex, "[{entry}] literal lexical form mismatch");
},
}
}
pub fn is_wildcard_vs_empty_string(
actual: &OxigraphInMemory,
a: &<OxigraphInMemory as Rdf>::Term,
expected: &OxigraphInMemory,
e: &<OxigraphInMemory as Rdf>::Term,
) -> bool {
fn is_empty_string(t: &<OxigraphInMemory as Rdf>::Term) -> bool {
matches!(Object::try_from(t.clone()), Ok(Object::Literal(lit)) if lit.lexical_form().is_empty())
}
fn is_wildcard_node(graph: &OxigraphInMemory, t: &<OxigraphInMemory as Rdf>::Term) -> bool {
let Ok(subj) = <OxigraphInMemory as Rdf>::Subject::try_from(t.clone()) else {
return false;
};
let Ok(triples) = graph.triples_with_subject(&subj) else {
return false;
};
let triples: Vec<_> = triples.collect();
triples.len() == 1
&& triples[0].pred().as_str() == RdfVocab::RDF_TYPE
&& triples[0].obj().to_string().contains(ShexRVocab::SX_WILDCARD)
}
(is_empty_string(a) && is_wildcard_node(expected, e)) || (is_empty_string(e) && is_wildcard_node(actual, a))
}
pub fn assert_same_node(
actual: &OxigraphInMemory,
a: <OxigraphInMemory as Rdf>::Term,
expected: &OxigraphInMemory,
e: <OxigraphInMemory as Rdf>::Term,
entry: &str,
visited: &mut std::collections::HashSet<(String, String)>,
) {
if is_wildcard_vs_empty_string(actual, &a, expected, &e) {
return;
}
if a.is_literal() || e.is_literal() {
assert_same_literal(a, e, entry);
return;
}
assert_eq!(a.is_iri(), e.is_iri(), "[{entry}] node kind mismatch: {a} vs {e}");
if a.is_iri() {
assert_eq!(a.to_string(), e.to_string(), "[{entry}] IRI mismatch");
}
if !visited.insert((a.to_string(), e.to_string())) {
return;
}
let a_subj: <OxigraphInMemory as Rdf>::Subject = a
.clone()
.try_into()
.unwrap_or_else(|_| panic!("[{entry}] {a} is not a subject"));
let e_subj: <OxigraphInMemory as Rdf>::Subject = e
.clone()
.try_into()
.unwrap_or_else(|_| panic!("[{entry}] {e} is not a subject"));
let mut a_triples: Vec<_> = actual
.triples_with_subject(&a_subj)
.expect("querying actual graph")
.map(|t| (t.pred().as_str().to_string(), t.obj().clone()))
.collect();
let mut e_triples: Vec<_> = expected
.triples_with_subject(&e_subj)
.expect("querying expected graph")
.map(|t| (t.pred().as_str().to_string(), t.obj().clone()))
.collect();
assert_eq!(
a_triples.len(),
e_triples.len(),
"[{entry}] different number of triples for {a} vs {e}: {a_triples:#?} vs {e_triples:#?}"
);
let sort_key = |t: &(String, <OxigraphInMemory as Rdf>::Term)| (t.0.clone(), t.1.to_string());
a_triples.sort_by_key(sort_key);
e_triples.sort_by_key(sort_key);
for ((a_pred, a_obj), (e_pred, e_obj)) in a_triples.into_iter().zip(e_triples) {
assert_eq!(a_pred, e_pred, "[{entry}] predicate mismatch on {a} vs {e}");
assert_same_node(actual, a_obj, expected, e_obj, entry, visited);
}
}