Skip to content

Commit a65434b

Browse files
committed
Execute Sparql constraints on properties even when the sh:path does not return any matches.
1 parent 84b225b commit a65434b

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

core/sds-aspect-model-validator/src/main/java/io/openmanufacturing/sds/aspectmodel/shacl/ShaclValidator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;
4242
import io.openmanufacturing.sds.aspectmodel.shacl.constraint.Constraint;
4343
import io.openmanufacturing.sds.aspectmodel.shacl.constraint.MinCountConstraint;
44+
import io.openmanufacturing.sds.aspectmodel.shacl.constraint.SparqlConstraint;
4445
import io.openmanufacturing.sds.aspectmodel.shacl.path.PathNodeRetriever;
4546
import io.openmanufacturing.sds.aspectmodel.shacl.path.PredicatePath;
4647
import io.openmanufacturing.sds.aspectmodel.shacl.violation.EvaluationContext;
@@ -178,6 +179,13 @@ public List<Violation> validateShapeForElement( final Resource element, final Sh
178179
violations.addAll( constraint.apply( assertion.getObject(), context ) );
179180
}
180181

182+
// important detail: Sparql constraints must run independent of whether there are any matches via the sh:path property or not
183+
// ( the check could be the verification whether the property exists )
184+
if ( reachableNodes.isEmpty() && constraint instanceof SparqlConstraint ) {
185+
final EvaluationContext context = new EvaluationContext( element, shape, Optional.empty(), List.of(), this );
186+
violations.addAll( constraint.apply( null, context ) );
187+
}
188+
181189
// MinCount needs to be handled separately: If the property is not used at all on the target node, but a MinCount constraints >= 1
182190
// exists, a violation must be emitted even though no value for the property exists
183191
if ( reachableNodes.isEmpty() && constraint instanceof MinCountConstraint && property.path() instanceof PredicatePath predicatePath ) {

core/sds-aspect-model-validator/src/test/java/io/openmanufacturing/sds/aspectmodel/shacl/ShaclValidatorTest.java

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1780,7 +1780,7 @@ void testSparqlTargetWithGenericConstraint() {
17801780
}
17811781

17821782
@Test
1783-
void testSparqlTargetWithSparqlConstraint() {
1783+
void testSparqlTargetWithShapeSparqlConstraint() {
17841784
final Model shapesModel = model( """
17851785
@prefix sh: <http://www.w3.org/ns/shacl#> .
17861786
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@@ -1845,6 +1845,71 @@ void testSparqlTargetWithSparqlConstraint() {
18451845
assertThat( finding ).isInstanceOf( SparqlConstraintViolation.class );
18461846
}
18471847

1848+
@Test
1849+
void testSparqlTargetWithPropertySparqlConstraint() {
1850+
final Model shapesModel = model( """
1851+
@prefix sh: <http://www.w3.org/ns/shacl#> .
1852+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
1853+
@prefix : <http://example.com#> .
1854+
1855+
:prefixDeclarations
1856+
sh:declare [
1857+
sh:prefix "" ;
1858+
sh:namespace "http://example.com#"^^xsd:anyURI ;
1859+
] .
1860+
1861+
:MyShape
1862+
a sh:NodeShape ;
1863+
sh:target [
1864+
a sh:SPARQLTarget ;
1865+
sh:prefixes :prefixDeclarations ;
1866+
sh:select ""\"
1867+
select $this
1868+
where {
1869+
$this a :TestClass .
1870+
}
1871+
""\"
1872+
] ;
1873+
sh:name "Test shape" ;
1874+
sh:description "Test shape description" ;
1875+
sh:property [
1876+
sh:path :testProperty ;
1877+
sh:datatype xsd:string ;
1878+
sh:sparql [
1879+
a sh:SPARQLConstraint ;
1880+
sh:message "Required property 'testProperty' does not exist on {$this}." ;
1881+
sh:prefixes :prefixDeclarations ;
1882+
sh:select ""\"
1883+
select $this ?code
1884+
where {
1885+
$this a :TestClass .
1886+
filter ( not exists { $this :testProperty [] } ) .
1887+
bind( "ERR_CUSTOM" as ?code )
1888+
}
1889+
""\"
1890+
] ;
1891+
] .
1892+
""" );
1893+
1894+
// important detail: ':testProperty' is missing on ':Foo', the SPARQLConstraint must run anyway
1895+
final Model dataModel = model( """
1896+
@prefix : <http://example.com#> .
1897+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
1898+
:Foo a :TestClass.
1899+
1900+
:Bar a :TestClass ;
1901+
:testProperty "secret valid value" .
1902+
""" );
1903+
1904+
final ShaclValidator validator = new ShaclValidator( shapesModel );
1905+
final Resource element = dataModel.createResource( namespace + "Foo" );
1906+
final List<Violation> violations = validator.validateElement( element );
1907+
1908+
assertThat( violations.size() ).isEqualTo( 1 );
1909+
final Violation finding = violations.get( 0 );
1910+
assertThat( finding ).isInstanceOf( SparqlConstraintViolation.class );
1911+
}
1912+
18481913
@Test
18491914
void testMultiElementValidation() {
18501915
final Model shapesModel = model( """

0 commit comments

Comments
 (0)