pub mod diagnostics;
pub mod graph;
pub mod lower;
pub mod path;
pub mod vocab;
pub use diagnostics::{DiagLevel, Diagnostic, ParseError};
pub use graph::{Loaded, RdfFormat};
use shifty_algebra::Schema;
pub struct ParseOutput {
pub schema: Schema,
pub diagnostics: Vec<Diagnostic>,
}
pub fn load_turtle(data: &[u8], base: Option<&str>) -> Result<Loaded, ParseError> {
Loaded::from_turtle(data, base)
}
pub fn load_ntriples(data: &[u8]) -> Result<Loaded, ParseError> {
Loaded::from_ntriples(data)
}
pub fn parse_turtle(data: &[u8], base: Option<&str>) -> Result<ParseOutput, ParseError> {
let loaded = Loaded::from_turtle(data, base)?;
let lowered = lower::lower(&loaded);
Ok(ParseOutput {
schema: lowered.schema,
diagnostics: lowered.diagnostics,
})
}
pub fn parse_loaded(loaded: &Loaded) -> ParseOutput {
let lowered = lower::lower(loaded);
ParseOutput {
schema: lowered.schema,
diagnostics: lowered.diagnostics,
}
}
#[cfg(test)]
mod tests {
use super::*;
use shifty_algebra::render::schema_to_text;
const SHAPES: &str = r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://ex/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:PersonShape a sh:NodeShape ;
sh:targetClass ex:Person ;
sh:property [
sh:path ex:name ;
sh:minCount 1 ;
sh:maxCount 1 ;
sh:datatype xsd:string ;
] ;
sh:property [
sh:path [ sh:inversePath ex:child ] ;
sh:nodeKind sh:IRI ;
] .
"#;
#[test]
fn lowers_person_shape() {
let out = parse_turtle(SHAPES.as_bytes(), None).unwrap();
assert!(out.diagnostics.is_empty(), "diags: {:?}", out.diagnostics);
let text = schema_to_text(&out.schema);
assert!(text.contains("rdf:type/rdfs:subClassOf*"), "text:\n{text}");
assert!(text.contains("[1..1] <http://ex/name>"), "text:\n{text}");
assert!(text.contains("^<http://ex/child>"), "text:\n{text}");
assert!(text.contains("datatype(xsd:string)"), "text:\n{text}");
}
#[test]
fn lowers_triple_rule() {
let ttl = r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://ex/> .
ex:S a sh:NodeShape ;
sh:targetClass ex:Rectangle ;
sh:rule [
a sh:TripleRule ;
sh:subject sh:this ;
sh:predicate ex:area ;
sh:object [ sh:path ex:width ] ;
sh:condition ex:S ;
sh:order 1 ;
] .
"#;
let out = parse_turtle(ttl.as_bytes(), None).unwrap();
assert!(out.diagnostics.is_empty(), "diags: {:?}", out.diagnostics);
assert_eq!(out.schema.rules.len(), 1);
let r = &out.schema.rules[0];
assert_eq!(r.order, Some(1));
assert_eq!(r.conditions.len(), 1);
use shifty_algebra::{NodeExpr, RuleHead};
match &r.head {
RuleHead::Triple {
subject,
predicate,
object,
} => {
assert!(matches!(subject, NodeExpr::This));
assert!(matches!(predicate, NodeExpr::Constant(_)));
assert!(matches!(object, NodeExpr::Path(_)));
}
other => panic!("expected TripleRule, got {other:?}"),
}
}
#[test]
fn lowers_sparql_rule_opaque() {
let ttl = r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://ex/> .
ex:S a sh:NodeShape ;
sh:targetNode ex:x ;
sh:rule [ a sh:SPARQLRule ; sh:construct "CONSTRUCT { ?this ex:p ?this } WHERE {}" ] .
"#;
let out = parse_turtle(ttl.as_bytes(), None).unwrap();
assert_eq!(out.schema.rules.len(), 1);
assert!(matches!(
out.schema.rules[0].head,
shifty_algebra::RuleHead::Sparql(_)
));
}
#[test]
fn lowers_sparql_constraint() {
let ttl = r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://ex/> .
ex:S a sh:NodeShape ;
sh:targetNode ex:x ;
sh:sparql [ sh:select "SELECT $this WHERE {}" ] .
"#;
let out = parse_turtle(ttl.as_bytes(), None).unwrap();
assert!(out.diagnostics.is_empty(), "diags: {:?}", out.diagnostics);
let root = out.schema.statements[0].shape;
assert!(matches!(
out.schema.arena.get(root),
shifty_algebra::Shape::Sparql(_)
));
}
#[test]
fn lowers_sparql_target() {
let ttl = r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://ex/> .
ex:S a sh:NodeShape ;
sh:target [ sh:select "SELECT ?this WHERE { ?this a ex:Person }" ] .
"#;
let out = parse_turtle(ttl.as_bytes(), None).unwrap();
assert!(out.diagnostics.is_empty(), "diags: {:?}", out.diagnostics);
assert!(matches!(
out.schema.statements[0].selector,
shifty_algebra::Selector::Sparql(_)
));
}
}