1pub mod diagnostics;
10pub mod graph;
11pub mod lower;
12pub mod path;
13pub mod vocab;
14
15pub use diagnostics::{DiagLevel, Diagnostic, ParseError};
16pub use graph::{Loaded, RdfFormat};
17
18use shifty_algebra::Schema;
19
20pub struct ParseOutput {
22 pub schema: Schema,
23 pub diagnostics: Vec<Diagnostic>,
24}
25
26pub fn load_turtle(data: &[u8], base: Option<&str>) -> Result<Loaded, ParseError> {
28 Loaded::from_turtle(data, base)
29}
30
31pub fn load_ntriples(data: &[u8]) -> Result<Loaded, ParseError> {
32 Loaded::from_ntriples(data)
33}
34
35pub fn parse_turtle(data: &[u8], base: Option<&str>) -> Result<ParseOutput, ParseError> {
37 let loaded = Loaded::from_turtle(data, base)?;
38 let lowered = lower::lower(&loaded);
39 Ok(ParseOutput {
40 schema: lowered.schema,
41 diagnostics: lowered.diagnostics,
42 })
43}
44
45pub fn parse_loaded(loaded: &Loaded) -> ParseOutput {
47 let lowered = lower::lower(loaded);
48 ParseOutput {
49 schema: lowered.schema,
50 diagnostics: lowered.diagnostics,
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57 use shifty_algebra::render::schema_to_text;
58
59 const SHAPES: &str = r#"
60 @prefix sh: <http://www.w3.org/ns/shacl#> .
61 @prefix ex: <http://ex/> .
62 @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
63
64 ex:PersonShape a sh:NodeShape ;
65 sh:targetClass ex:Person ;
66 sh:property [
67 sh:path ex:name ;
68 sh:minCount 1 ;
69 sh:maxCount 1 ;
70 sh:datatype xsd:string ;
71 ] ;
72 sh:property [
73 sh:path [ sh:inversePath ex:child ] ;
74 sh:nodeKind sh:IRI ;
75 ] .
76 "#;
77
78 #[test]
79 fn lowers_person_shape() {
80 let out = parse_turtle(SHAPES.as_bytes(), None).unwrap();
81 assert!(out.diagnostics.is_empty(), "diags: {:?}", out.diagnostics);
82
83 let text = schema_to_text(&out.schema);
84 assert!(text.contains("rdf:type/rdfs:subClassOf*"), "text:\n{text}");
86 assert!(text.contains("[1..1] <http://ex/name>"), "text:\n{text}");
88 assert!(text.contains("^<http://ex/child>"), "text:\n{text}");
90 assert!(text.contains("datatype(xsd:string)"), "text:\n{text}");
92 }
93
94 #[test]
95 fn lowers_triple_rule() {
96 let ttl = r#"
97 @prefix sh: <http://www.w3.org/ns/shacl#> .
98 @prefix ex: <http://ex/> .
99 ex:S a sh:NodeShape ;
100 sh:targetClass ex:Rectangle ;
101 sh:rule [
102 a sh:TripleRule ;
103 sh:subject sh:this ;
104 sh:predicate ex:area ;
105 sh:object [ sh:path ex:width ] ;
106 sh:condition ex:S ;
107 sh:order 1 ;
108 ] .
109 "#;
110 let out = parse_turtle(ttl.as_bytes(), None).unwrap();
111 assert!(out.diagnostics.is_empty(), "diags: {:?}", out.diagnostics);
112 assert_eq!(out.schema.rules.len(), 1);
113 let r = &out.schema.rules[0];
114 assert_eq!(r.order, Some(1));
115 assert_eq!(r.conditions.len(), 1);
116 use shifty_algebra::{NodeExpr, RuleHead};
117 match &r.head {
118 RuleHead::Triple {
119 subject,
120 predicate,
121 object,
122 } => {
123 assert!(matches!(subject, NodeExpr::This));
124 assert!(matches!(predicate, NodeExpr::Constant(_)));
125 assert!(matches!(object, NodeExpr::Path(_)));
126 }
127 other => panic!("expected TripleRule, got {other:?}"),
128 }
129 }
130
131 #[test]
132 fn lowers_sparql_rule_opaque() {
133 let ttl = r#"
134 @prefix sh: <http://www.w3.org/ns/shacl#> .
135 @prefix ex: <http://ex/> .
136 ex:S a sh:NodeShape ;
137 sh:targetNode ex:x ;
138 sh:rule [ a sh:SPARQLRule ; sh:construct "CONSTRUCT { ?this ex:p ?this } WHERE {}" ] .
139 "#;
140 let out = parse_turtle(ttl.as_bytes(), None).unwrap();
141 assert_eq!(out.schema.rules.len(), 1);
142 assert!(matches!(
143 out.schema.rules[0].head,
144 shifty_algebra::RuleHead::Sparql(_)
145 ));
146 }
147
148 #[test]
149 fn lowers_sparql_constraint() {
150 let ttl = r#"
151 @prefix sh: <http://www.w3.org/ns/shacl#> .
152 @prefix ex: <http://ex/> .
153 ex:S a sh:NodeShape ;
154 sh:targetNode ex:x ;
155 sh:sparql [ sh:select "SELECT $this WHERE {}" ] .
156 "#;
157 let out = parse_turtle(ttl.as_bytes(), None).unwrap();
158 assert!(out.diagnostics.is_empty(), "diags: {:?}", out.diagnostics);
159 let root = out.schema.statements[0].shape;
160 assert!(matches!(
161 out.schema.arena.get(root),
162 shifty_algebra::Shape::Sparql(_)
163 ));
164 }
165
166 #[test]
167 fn lowers_sparql_target() {
168 let ttl = r#"
169 @prefix sh: <http://www.w3.org/ns/shacl#> .
170 @prefix ex: <http://ex/> .
171 ex:S a sh:NodeShape ;
172 sh:target [ sh:select "SELECT ?this WHERE { ?this a ex:Person }" ] .
173 "#;
174 let out = parse_turtle(ttl.as_bytes(), None).unwrap();
175 assert!(out.diagnostics.is_empty(), "diags: {:?}", out.diagnostics);
176 assert!(matches!(
177 out.schema.statements[0].selector,
178 shifty_algebra::Selector::Sparql(_)
179 ));
180 }
181}