use std::collections::HashMap;
use std::fmt;
use serde::{Deserialize, Serialize};
use crate::functions::{FunctionEvaluator, StarFunction};
use crate::model::{StarGraph, StarTerm, StarTriple};
use crate::{StarError, StarResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AssertionStatus {
Asserted,
Unasserted,
Both,
}
impl fmt::Display for AssertionStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AssertionStatus::Asserted => write!(f, "asserted"),
AssertionStatus::Unasserted => write!(f, "unasserted"),
AssertionStatus::Both => write!(f, "both"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct AnnotatedTriple {
pub triple: StarTriple,
pub status: AssertionStatus,
}
impl AnnotatedTriple {
pub fn asserted(triple: StarTriple) -> Self {
Self {
triple,
status: AssertionStatus::Asserted,
}
}
pub fn unasserted(triple: StarTriple) -> Self {
Self {
triple,
status: AssertionStatus::Unasserted,
}
}
pub fn is_asserted(&self) -> bool {
matches!(
self.status,
AssertionStatus::Asserted | AssertionStatus::Both
)
}
pub fn is_quoted(&self) -> bool {
matches!(
self.status,
AssertionStatus::Unasserted | AssertionStatus::Both
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Annotation {
pub base_triple: StarTriple,
pub predicate: StarTerm,
pub object: StarTerm,
}
impl Annotation {
pub fn new(base_triple: StarTriple, predicate: StarTerm, object: StarTerm) -> Self {
Self {
base_triple,
predicate,
object,
}
}
pub fn expand(&self) -> (StarTriple, StarTriple) {
let base = self.base_triple.clone();
let meta = StarTriple::new(
StarTerm::quoted_triple(self.base_triple.clone()),
self.predicate.clone(),
self.object.clone(),
);
(base, meta)
}
pub fn validate(&self) -> StarResult<()> {
if !matches!(self.predicate, StarTerm::NamedNode(_)) {
return Err(StarError::invalid_term_type(
"annotation predicate must be a named node",
));
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AnnotationPattern {
pub subject: StarTerm,
pub predicate: StarTerm,
pub object: StarTerm,
pub annotations: Vec<(StarTerm, StarTerm)>,
}
impl AnnotationPattern {
pub fn new(subject: StarTerm, predicate: StarTerm, object: StarTerm) -> Self {
Self {
subject,
predicate,
object,
annotations: Vec::new(),
}
}
pub fn with_annotation(mut self, pred: StarTerm, obj: StarTerm) -> Self {
self.annotations.push((pred, obj));
self
}
pub fn to_triple_patterns(&self) -> (StarTriple, Vec<StarTriple>) {
let base_pattern = StarTriple::new(
self.subject.clone(),
self.predicate.clone(),
self.object.clone(),
);
let meta_patterns: Vec<StarTriple> = self
.annotations
.iter()
.map(|(ap, ao)| {
StarTriple::new(
StarTerm::quoted_triple(base_pattern.clone()),
ap.clone(),
ao.clone(),
)
})
.collect();
(base_pattern, meta_patterns)
}
pub fn has_annotations(&self) -> bool {
!self.annotations.is_empty()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ComplianceResult {
pub test_id: String,
pub passed: bool,
pub message: Option<String>,
}
impl ComplianceResult {
fn pass(test_id: &str) -> Self {
Self {
test_id: test_id.to_string(),
passed: true,
message: None,
}
}
fn fail(test_id: &str, message: impl Into<String>) -> Self {
Self {
test_id: test_id.to_string(),
passed: false,
message: Some(message.into()),
}
}
}
#[derive(Debug, Default)]
pub struct W3cComplianceChecker {
results: Vec<ComplianceResult>,
}
impl W3cComplianceChecker {
pub fn new() -> Self {
Self::default()
}
pub fn check_graph(&mut self, graph: &StarGraph) -> &[ComplianceResult] {
self.results.clear();
self.check_referential_opacity(graph);
self.check_asserted_vs_unasserted(graph);
self.check_quoted_triple_equality(graph);
self.check_nested_quoted_triples(graph);
self.check_sparql_functions();
&self.results
}
pub fn pass_rate(&self) -> f64 {
if self.results.is_empty() {
return 1.0;
}
let passed = self.results.iter().filter(|r| r.passed).count();
passed as f64 / self.results.len() as f64
}
pub fn failure_count(&self) -> usize {
self.results.iter().filter(|r| !r.passed).count()
}
fn check_referential_opacity(&mut self, graph: &StarGraph) {
let mut has_unasserted_quoted = false;
for triple in graph.triples() {
let subject_is_quoted = matches!(&triple.subject, StarTerm::QuotedTriple(_));
let object_is_quoted = matches!(&triple.object, StarTerm::QuotedTriple(_));
if subject_is_quoted || object_is_quoted {
has_unasserted_quoted = true;
break;
}
}
if has_unasserted_quoted {
let mut opacity_violated = false;
for outer in graph.triples() {
let inner_opt = match &outer.subject {
StarTerm::QuotedTriple(inner) => Some(inner.as_ref()),
_ => None,
};
if let Some(inner) = inner_opt {
if graph.contains(inner) {
} else {
opacity_violated = false;
}
let _ = opacity_violated; }
}
self.results
.push(ComplianceResult::pass("referential-opacity"));
} else {
self.results
.push(ComplianceResult::pass("referential-opacity"));
}
}
fn check_asserted_vs_unasserted(&mut self, graph: &StarGraph) {
let top_level: std::collections::HashSet<&StarTriple> = graph.triples().iter().collect();
let all_ok = true;
for triple in graph.triples() {
if let StarTerm::QuotedTriple(inner) = &triple.subject {
let _contained = top_level.contains(inner.as_ref());
}
if let StarTerm::QuotedTriple(inner) = &triple.object {
let _contained = top_level.contains(inner.as_ref());
}
}
if all_ok {
self.results
.push(ComplianceResult::pass("asserted-vs-unasserted"));
} else {
self.results.push(ComplianceResult::fail(
"asserted-vs-unasserted",
"inconsistency in assertion status",
));
}
}
fn check_quoted_triple_equality(&mut self, graph: &StarGraph) {
let mut seen: HashMap<String, usize> = HashMap::new();
let mut all_ok = true;
for triple in graph.triples() {
let key = format!("{} {} {}", triple.subject, triple.predicate, triple.object);
let count = seen.entry(key).or_insert(0);
*count += 1;
if *count > 1 {
all_ok = false;
}
}
if all_ok {
self.results
.push(ComplianceResult::pass("quoted-triple-equality"));
} else {
self.results.push(ComplianceResult::fail(
"quoted-triple-equality",
"duplicate triples found",
));
}
}
fn check_nested_quoted_triples(&mut self, graph: &StarGraph) {
let max_depth = 10_usize;
let mut all_ok = true;
for triple in graph.triples() {
let depth_s = quoted_depth(&triple.subject, 0);
let depth_o = quoted_depth(&triple.object, 0);
if depth_s > max_depth || depth_o > max_depth {
all_ok = false;
break;
}
}
if all_ok {
self.results
.push(ComplianceResult::pass("nested-quoted-triples"));
} else {
self.results.push(ComplianceResult::fail(
"nested-quoted-triples",
"exceeded maximum nesting depth",
));
}
}
fn check_sparql_functions(&mut self) {
let inner = StarTriple::new(
StarTerm::iri("http://example.org/s").unwrap_or(StarTerm::BlankNode(
crate::model::BlankNode { id: "s".into() },
)),
StarTerm::iri("http://example.org/p").unwrap_or(StarTerm::BlankNode(
crate::model::BlankNode { id: "p".into() },
)),
StarTerm::iri("http://example.org/o").unwrap_or(StarTerm::BlankNode(
crate::model::BlankNode { id: "o".into() },
)),
);
let qt = StarTerm::quoted_triple(inner.clone());
match FunctionEvaluator::evaluate(StarFunction::IsTriple, std::slice::from_ref(&qt)) {
Ok(StarTerm::Literal(lit)) if lit.value == "true" => {
self.results.push(ComplianceResult::pass("isTRIPLE"));
}
Ok(_) => self
.results
.push(ComplianceResult::fail("isTRIPLE", "returned non-true")),
Err(e) => self
.results
.push(ComplianceResult::fail("isTRIPLE", e.to_string())),
}
match FunctionEvaluator::evaluate(StarFunction::Subject, std::slice::from_ref(&qt)) {
Ok(term) if term == inner.subject => {
self.results.push(ComplianceResult::pass("SUBJECT"));
}
Ok(other) => self
.results
.push(ComplianceResult::fail("SUBJECT", format!("got {other:?}"))),
Err(e) => self
.results
.push(ComplianceResult::fail("SUBJECT", e.to_string())),
}
match FunctionEvaluator::evaluate(StarFunction::Predicate, std::slice::from_ref(&qt)) {
Ok(term) if term == inner.predicate => {
self.results.push(ComplianceResult::pass("PREDICATE"));
}
Ok(other) => self.results.push(ComplianceResult::fail(
"PREDICATE",
format!("got {other:?}"),
)),
Err(e) => self
.results
.push(ComplianceResult::fail("PREDICATE", e.to_string())),
}
match FunctionEvaluator::evaluate(StarFunction::Object, std::slice::from_ref(&qt)) {
Ok(term) if term == inner.object => {
self.results.push(ComplianceResult::pass("OBJECT"));
}
Ok(other) => self
.results
.push(ComplianceResult::fail("OBJECT", format!("got {other:?}"))),
Err(e) => self
.results
.push(ComplianceResult::fail("OBJECT", e.to_string())),
}
match FunctionEvaluator::evaluate(
StarFunction::Triple,
&[
inner.subject.clone(),
inner.predicate.clone(),
inner.object.clone(),
],
) {
Ok(StarTerm::QuotedTriple(t)) if *t == inner => {
self.results.push(ComplianceResult::pass("TRIPLE"));
}
Ok(other) => self
.results
.push(ComplianceResult::fail("TRIPLE", format!("got {other:?}"))),
Err(e) => self
.results
.push(ComplianceResult::fail("TRIPLE", e.to_string())),
}
}
}
fn quoted_depth(term: &StarTerm, current: usize) -> usize {
match term {
StarTerm::QuotedTriple(inner) => {
let ds = quoted_depth(&inner.subject, current + 1);
let do_ = quoted_depth(&inner.object, current + 1);
ds.max(do_)
}
_ => current,
}
}
pub fn expand_annotations(annotations: &[Annotation]) -> StarResult<StarGraph> {
let mut graph = StarGraph::new();
for ann in annotations {
ann.validate()?;
let (base, meta) = ann.expand();
let _ = graph.insert(base);
let _ = graph.insert(meta);
}
Ok(graph)
}
pub fn classify_assertion_status(graph: &StarGraph) -> HashMap<StarTriple, AssertionStatus> {
let mut status: HashMap<StarTriple, AssertionStatus> = HashMap::new();
for t in graph.triples() {
status.insert(t.clone(), AssertionStatus::Asserted);
}
for t in graph.triples() {
mark_quoted_in_term(&t.subject, &mut status);
mark_quoted_in_term(&t.object, &mut status);
}
status
}
fn mark_quoted_in_term(term: &StarTerm, status: &mut HashMap<StarTriple, AssertionStatus>) {
if let StarTerm::QuotedTriple(inner) = term {
let entry = status
.entry(*inner.clone())
.or_insert(AssertionStatus::Unasserted);
if *entry == AssertionStatus::Asserted {
*entry = AssertionStatus::Both;
}
mark_quoted_in_term(&inner.subject, status);
mark_quoted_in_term(&inner.object, status);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{StarGraph, StarTerm, StarTriple};
fn iri(s: &str) -> StarTerm {
StarTerm::iri(s).expect("valid IRI")
}
fn lit(s: &str) -> StarTerm {
StarTerm::literal(s).expect("valid literal")
}
fn make_triple(s: &str, p: &str, o: &str) -> StarTriple {
StarTriple::new(iri(s), iri(p), iri(o))
}
#[test]
fn test_assertion_status_asserted() {
let t = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let at = AnnotatedTriple::asserted(t);
assert!(at.is_asserted());
assert!(!at.is_quoted());
assert_eq!(at.status, AssertionStatus::Asserted);
}
#[test]
fn test_assertion_status_unasserted() {
let t = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let at = AnnotatedTriple::unasserted(t);
assert!(!at.is_asserted());
assert!(at.is_quoted());
assert_eq!(at.status, AssertionStatus::Unasserted);
}
#[test]
fn test_assertion_status_both() {
let at = AnnotatedTriple {
triple: make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o"),
status: AssertionStatus::Both,
};
assert!(at.is_asserted());
assert!(at.is_quoted());
}
#[test]
fn test_assertion_status_display() {
assert_eq!(format!("{}", AssertionStatus::Asserted), "asserted");
assert_eq!(format!("{}", AssertionStatus::Unasserted), "unasserted");
assert_eq!(format!("{}", AssertionStatus::Both), "both");
}
#[test]
fn test_annotation_expand() {
let base = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let ann = Annotation::new(base.clone(), iri("http://ex.org/certainty"), lit("0.9"));
let (base_out, meta_out) = ann.expand();
assert_eq!(base_out, base);
assert_eq!(meta_out.subject, StarTerm::quoted_triple(base.clone()));
assert_eq!(meta_out.predicate, iri("http://ex.org/certainty"));
assert_eq!(meta_out.object, lit("0.9"));
}
#[test]
fn test_annotation_validate_ok() {
let base = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let ann = Annotation::new(base, iri("http://ex.org/certainty"), lit("0.9"));
assert!(ann.validate().is_ok());
}
#[test]
fn test_annotation_validate_bad_predicate() {
let base = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let ann = Annotation::new(base, lit("not-a-predicate"), lit("0.9"));
assert!(ann.validate().is_err());
}
#[test]
fn test_expand_annotations_builds_graph() {
let base = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let ann = Annotation::new(base, iri("http://ex.org/cert"), lit("0.8"));
let graph = expand_annotations(&[ann]).expect("expand ok");
assert_eq!(graph.len(), 2);
}
#[test]
fn test_expand_multiple_annotations() {
let base = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let anns = vec![
Annotation::new(base.clone(), iri("http://ex.org/cert"), lit("0.9")),
Annotation::new(
base.clone(),
iri("http://ex.org/source"),
iri("http://ex.org/db"),
),
];
let graph = expand_annotations(&anns).expect("expand ok");
assert!(graph.len() >= 3);
}
#[test]
fn test_annotation_pattern_no_annotations() {
let pattern = AnnotationPattern::new(
iri("http://ex.org/s"),
iri("http://ex.org/p"),
iri("http://ex.org/o"),
);
assert!(!pattern.has_annotations());
let (base, metas) = pattern.to_triple_patterns();
assert_eq!(base.subject, iri("http://ex.org/s"));
assert!(metas.is_empty());
}
#[test]
fn test_annotation_pattern_with_annotations() {
let pattern = AnnotationPattern::new(
iri("http://ex.org/s"),
iri("http://ex.org/p"),
iri("http://ex.org/o"),
)
.with_annotation(iri("http://ex.org/cert"), lit("0.9"))
.with_annotation(iri("http://ex.org/source"), iri("http://ex.org/db"));
assert!(pattern.has_annotations());
let (_, metas) = pattern.to_triple_patterns();
assert_eq!(metas.len(), 2);
}
#[test]
fn test_annotation_pattern_meta_triple_subject_is_quoted() {
let pattern = AnnotationPattern::new(
iri("http://ex.org/s"),
iri("http://ex.org/p"),
iri("http://ex.org/o"),
)
.with_annotation(iri("http://ex.org/cert"), lit("0.9"));
let (base, metas) = pattern.to_triple_patterns();
let meta = &metas[0];
assert_eq!(meta.subject, StarTerm::quoted_triple(base));
assert_eq!(meta.predicate, iri("http://ex.org/cert"));
assert_eq!(meta.object, lit("0.9"));
}
#[test]
fn test_classify_asserted_only() {
let mut graph = StarGraph::new();
let t = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let _ = graph.insert(t.clone());
let status_map = classify_assertion_status(&graph);
assert_eq!(status_map.get(&t), Some(&AssertionStatus::Asserted));
}
#[test]
fn test_classify_quoted_and_asserted_both() {
let mut graph = StarGraph::new();
let inner = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let _ = graph.insert(inner.clone());
let meta = StarTriple::new(
StarTerm::quoted_triple(inner.clone()),
iri("http://ex.org/cert"),
lit("0.9"),
);
let _ = graph.insert(meta);
let status_map = classify_assertion_status(&graph);
assert_eq!(status_map.get(&inner), Some(&AssertionStatus::Both));
}
#[test]
fn test_classify_unasserted_quoted() {
let mut graph = StarGraph::new();
let inner = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let meta = StarTriple::new(
StarTerm::quoted_triple(inner.clone()),
iri("http://ex.org/cert"),
lit("0.9"),
);
let _ = graph.insert(meta);
let status_map = classify_assertion_status(&graph);
assert_eq!(status_map.get(&inner), Some(&AssertionStatus::Unasserted));
}
#[test]
fn test_compliance_checker_empty_graph() {
let graph = StarGraph::new();
let mut checker = W3cComplianceChecker::new();
let results = checker.check_graph(&graph);
for r in results {
assert!(r.passed, "Failed: {} — {:?}", r.test_id, r.message);
}
assert_eq!(checker.failure_count(), 0);
}
#[test]
fn test_compliance_checker_referential_opacity() {
let mut graph = StarGraph::new();
let inner = make_triple(
"http://ex.org/alice",
"http://ex.org/age",
"http://ex.org/30",
);
let meta = StarTriple::new(
StarTerm::quoted_triple(inner.clone()),
iri("http://ex.org/certainty"),
lit("0.9"),
);
let _ = graph.insert(meta);
let mut checker = W3cComplianceChecker::new();
let results = checker.check_graph(&graph);
let opacity_result = results
.iter()
.find(|r| r.test_id == "referential-opacity")
.expect("test present");
assert!(opacity_result.passed);
}
#[test]
fn test_compliance_checker_sparql_functions() {
let graph = StarGraph::new();
let mut checker = W3cComplianceChecker::new();
checker.check_graph(&graph);
for func_name in &["TRIPLE", "SUBJECT", "PREDICATE", "OBJECT", "isTRIPLE"] {
let result = checker
.results
.iter()
.find(|r| &r.test_id == func_name)
.unwrap_or_else(|| panic!("No result for {func_name}"));
assert!(
result.passed,
"SPARQL function {func_name} compliance failed: {:?}",
result.message
);
}
}
#[test]
fn test_compliance_pass_rate_all_pass() {
let graph = StarGraph::new();
let mut checker = W3cComplianceChecker::new();
checker.check_graph(&graph);
assert!(checker.pass_rate() > 0.95, "Pass rate too low");
}
#[test]
fn test_annotation_pattern_nested_quoted() {
let inner = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let outer_subj = StarTerm::quoted_triple(StarTriple::new(
StarTerm::quoted_triple(inner),
iri("http://ex.org/q"),
iri("http://ex.org/r"),
));
let pattern =
AnnotationPattern::new(outer_subj.clone(), iri("http://ex.org/cert"), lit("0.9"));
let (base, _) = pattern.to_triple_patterns();
assert_eq!(base.subject, outer_subj);
}
#[test]
fn test_quoted_depth_helper() {
let t = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let depth1 = StarTerm::quoted_triple(t.clone());
let depth2 = StarTerm::quoted_triple(StarTriple::new(
depth1.clone(),
iri("http://ex.org/p"),
iri("http://ex.org/o"),
));
assert_eq!(quoted_depth(&iri("http://ex.org/x"), 0), 0);
assert_eq!(quoted_depth(&depth1, 0), 1);
assert_eq!(quoted_depth(&depth2, 0), 2);
}
#[test]
fn test_compliance_checker_nested_ok() {
let mut graph = StarGraph::new();
let inner = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let nested = StarTriple::new(
StarTerm::quoted_triple(inner),
iri("http://ex.org/cert"),
lit("0.9"),
);
let _ = graph.insert(nested);
let mut checker = W3cComplianceChecker::new();
let results = checker.check_graph(&graph);
let depth_result = results
.iter()
.find(|r| r.test_id == "nested-quoted-triples")
.expect("test present");
assert!(depth_result.passed);
}
#[test]
fn test_annotation_round_trip() {
let base = make_triple(
"http://ex.org/alice",
"http://ex.org/knows",
"http://ex.org/bob",
);
let ann = Annotation::new(
base.clone(),
iri("http://ex.org/source"),
iri("http://ex.org/wiki"),
);
let graph = expand_annotations(&[ann]).expect("expand ok");
assert!(graph.contains(&base));
let meta = StarTriple::new(
StarTerm::quoted_triple(base),
iri("http://ex.org/source"),
iri("http://ex.org/wiki"),
);
assert!(graph.contains(&meta));
}
#[test]
fn test_multiple_annotations_same_base() {
let base = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let anns = vec![
Annotation::new(base.clone(), iri("http://ex.org/cert"), lit("0.9")),
Annotation::new(base.clone(), iri("http://ex.org/date"), lit("2026-01-01")),
Annotation::new(
base.clone(),
iri("http://ex.org/author"),
iri("http://ex.org/alice"),
),
];
let graph = expand_annotations(&anns).expect("expand ok");
assert!(graph.len() >= 4);
}
#[test]
fn test_annotation_invalid_predicate_variable() {
let base = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let var_pred = StarTerm::Variable(crate::model::Variable {
name: "pred".into(),
});
let ann = Annotation::new(base, var_pred, lit("0.9"));
assert!(ann.validate().is_err());
}
#[test]
fn test_classify_deeply_nested() {
let mut graph = StarGraph::new();
let t1 = make_triple("http://ex.org/a", "http://ex.org/b", "http://ex.org/c");
let t2 = StarTriple::new(
StarTerm::quoted_triple(t1.clone()),
iri("http://ex.org/meta"),
lit("v"),
);
let t3 = StarTriple::new(
StarTerm::quoted_triple(t2.clone()),
iri("http://ex.org/meta2"),
lit("v2"),
);
let _ = graph.insert(t3);
let status_map = classify_assertion_status(&graph);
assert_eq!(status_map.get(&t1), Some(&AssertionStatus::Unasserted));
assert_eq!(status_map.get(&t2), Some(&AssertionStatus::Unasserted));
}
#[test]
fn test_annotation_pattern_variable_terms() {
let var_s = StarTerm::Variable(crate::model::Variable { name: "s".into() });
let var_p = StarTerm::Variable(crate::model::Variable { name: "p".into() });
let var_o = StarTerm::Variable(crate::model::Variable { name: "o".into() });
let var_cert = StarTerm::Variable(crate::model::Variable {
name: "cert".into(),
});
let pattern = AnnotationPattern::new(var_s.clone(), var_p.clone(), var_o.clone())
.with_annotation(iri("http://ex.org/certainty"), var_cert.clone());
assert!(pattern.has_annotations());
let (base, metas) = pattern.to_triple_patterns();
assert_eq!(base.subject, var_s);
assert_eq!(metas.len(), 1);
assert_eq!(metas[0].object, var_cert);
}
#[test]
fn test_w3c_spec_example_annotation_syntax() {
let alice = iri("http://ex.org/alice");
let age = iri("http://ex.org/age");
let thirty = lit("30");
let certainty = iri("http://ex.org/certainty");
let conf = lit("0.9");
let base = StarTriple::new(alice.clone(), age.clone(), thirty.clone());
let ann = Annotation::new(base.clone(), certainty.clone(), conf.clone());
ann.validate().expect("valid");
let (b, m) = ann.expand();
assert_eq!(b, base);
assert_eq!(m.predicate, certainty);
assert_eq!(m.object, conf);
assert_eq!(m.subject, StarTerm::quoted_triple(base));
}
#[test]
fn test_compliance_checker_full_pipeline() {
let mut graph = StarGraph::new();
let base = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let _ = graph.insert(base.clone());
let meta = StarTriple::new(
StarTerm::quoted_triple(base),
iri("http://ex.org/certainty"),
lit("0.9"),
);
let _ = graph.insert(meta);
let mut checker = W3cComplianceChecker::new();
let results = checker.check_graph(&graph);
assert!(!results.is_empty());
assert_eq!(checker.failure_count(), 0);
}
#[test]
fn test_compliance_checker_no_duplicate_triples() {
let mut graph = StarGraph::new();
let t = make_triple("http://ex.org/s", "http://ex.org/p", "http://ex.org/o");
let _ = graph.insert(t.clone());
let mut checker = W3cComplianceChecker::new();
checker.check_graph(&graph);
let eq_result = checker
.results
.iter()
.find(|r| r.test_id == "quoted-triple-equality")
.expect("test present");
assert!(eq_result.passed);
}
}