Skip to content

Commit

Permalink
GH-4754 improve error handling in ShaclProperties class (#4755)
Browse files Browse the repository at this point in the history
  • Loading branch information
hmottestad authored Aug 24, 2023
2 parents 7ad3ebf + 242da16 commit 0ead97f
Show file tree
Hide file tree
Showing 8 changed files with 817 additions and 96 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*******************************************************************************
* Copyright (c) 2023 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
******************************************************************************/

package org.eclipse.rdf4j.sail.shacl.ast;

import org.eclipse.rdf4j.common.exception.RDF4JException;
import org.eclipse.rdf4j.model.Resource;

/**
* An exception indicating that something went wrong when parsing a shape. The id field contains the subject of the
* shape statements.
*/
public class ShaclShapeParsingException extends RDF4JException {

Resource id;

public ShaclShapeParsingException(String msg, Resource id) {
super(msg + " - Caused by shape with id: " + resourceToString(id));
this.id = id;
}

public ShaclShapeParsingException(String msg, Throwable t, Resource id) {
super(msg + " - Caused by shape with id: " + resourceToString(id), t);
this.id = id;
}

public ShaclShapeParsingException(Throwable t, Resource id) {
super(t.getMessage() + " - Caused by shape with id: " + resourceToString(id), t);
this.id = id;
}

public Resource getId() {
return id;
}

private static String resourceToString(Resource id) {
assert id != null;
if (id == null) {
return "null";
}
if (id.isIRI()) {
return "<" + id.stringValue() + ">";
}
if (id.isBNode()) {
return id.toString();
}
if (id.isTriple()) {
return "TRIPLE " + id;
}
return id.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,8 @@ List<ConstraintComponent> getConstraintComponents(ShaclProperties properties, Sh
constraintComponent.add(new UniqueLangConstraintComponent());
}

for (String pattern : properties.getPattern()) {
var patternConstraintComponent = new PatternConstraintComponent(pattern,
if (properties.getPattern() != null) {
var patternConstraintComponent = new PatternConstraintComponent(properties.getPattern(),
properties.getFlags());
constraintComponent.add(patternConstraintComponent);
}
Expand Down Expand Up @@ -657,14 +657,28 @@ public static ContextWithShapes getShapesInContext(ShapeSource shapeSource, Pars

try (Stream<Resource> resources = shapeSourceWithContext.getTargetableShape()) {
List<Shape> shapes = resources
.map(r -> new ShaclProperties(r, shapeSourceWithContext))
.map(r -> {
try {
return new ShaclProperties(r, shapeSourceWithContext);
} catch (Exception e) {
throw new ShaclShapeParsingException(e, r);
}
})
.map(p -> {
if (p.getType() == SHACL.NODE_SHAPE) {
return NodeShape.getInstance(p, shapeSourceWithContext, parseSettings, cache);
} else if (p.getType() == SHACL.PROPERTY_SHAPE) {
return PropertyShape.getInstance(p, shapeSourceWithContext, parseSettings, cache);
try {
if (p.getType() == SHACL.NODE_SHAPE) {
return NodeShape.getInstance(p, shapeSourceWithContext, parseSettings, cache);
} else if (p.getType() == SHACL.PROPERTY_SHAPE) {
return PropertyShape.getInstance(p, shapeSourceWithContext, parseSettings, cache);
}
throw new ShaclShapeParsingException("Unknown shape type", p.getId());
} catch (Exception e) {
if (e instanceof ShaclShapeParsingException) {
throw e;
}
throw new ShaclShapeParsingException(e, p.getId());
}
throw new IllegalStateException("Unknown shape type for " + p.getId());

})
.collect(Collectors.toList());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@

public class QualifiedMaxCountConstraintComponent extends AbstractConstraintComponent {
Shape qualifiedValueShape;
Boolean qualifiedValueShapesDisjoint;
boolean qualifiedValueShapesDisjoint;
Long qualifiedMaxCount;

public QualifiedMaxCountConstraintComponent(Resource id, ShapeSource shapeSource,
Expand Down Expand Up @@ -84,7 +84,7 @@ public QualifiedMaxCountConstraintComponent(QualifiedMaxCountConstraintComponent
public void toModel(Resource subject, IRI predicate, Model model, Set<Resource> cycleDetection) {
model.add(subject, SHACL.QUALIFIED_VALUE_SHAPE, getId());

if (qualifiedValueShapesDisjoint != null) {
if (qualifiedValueShapesDisjoint) {
model.add(subject, SHACL.QUALIFIED_VALUE_SHAPES_DISJOINT, literal(qualifiedValueShapesDisjoint));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@

public class QualifiedMinCountConstraintComponent extends AbstractConstraintComponent {
Shape qualifiedValueShape;
Boolean qualifiedValueShapesDisjoint;
boolean qualifiedValueShapesDisjoint;
Long qualifiedMinCount;

public QualifiedMinCountConstraintComponent(Resource id, ShapeSource shapeSource,
Expand Down Expand Up @@ -87,7 +87,7 @@ public QualifiedMinCountConstraintComponent(QualifiedMinCountConstraintComponent
public void toModel(Resource subject, IRI predicate, Model model, Set<Resource> cycleDetection) {
model.add(subject, SHACL.QUALIFIED_VALUE_SHAPE, getId());

if (qualifiedValueShapesDisjoint != null) {
if (qualifiedValueShapesDisjoint) {
model.add(subject, SHACL.QUALIFIED_VALUE_SHAPES_DISJOINT, literal(qualifiedValueShapesDisjoint));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*******************************************************************************
* Copyright (c) 2023 Eclipse RDF4J contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
******************************************************************************/

package org.eclipse.rdf4j.sail.shacl.ast;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import java.util.stream.Stream;

import org.eclipse.rdf4j.model.*;
import org.eclipse.rdf4j.model.util.Values;
import org.eclipse.rdf4j.sail.shacl.wrapper.shape.ShapeSource;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
public class ShaclPropertiesCastingTest {

public static final Literal DUMMY_VALUE = Values.literal("dummy value");
public static final IRI IRI = Values.iri("http://example.org/iri");
@Mock
private Statement statement;

@Mock
private ShapeSource shapeSource;

@InjectMocks
private ShaclProperties shaclProperties;

@ParameterizedTest
@MethodSource("provideArgumentsForTest")
public void testConstructor(IRI predicate, Value value, String exceptedMessage) {
String exceptedMessageWithPredicate = String.format(exceptedMessage, predicate);

when(shapeSource.getAllStatements(any(Resource.class))).thenReturn(Stream.of(statement));
when(statement.getPredicate()).thenReturn(predicate);
when(statement.getObject()).thenReturn(value);

ShaclShapeParsingException exception = assertThrows(ShaclShapeParsingException.class,
() -> new ShaclProperties(Values.iri("http://example.org/shape1"), shapeSource)
);

assertEquals(exceptedMessageWithPredicate, exception.getMessage());

}

private static Stream<Arguments> provideArgumentsForTest() {
return Stream.of(
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#or"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#xone"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#and"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#not"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#property"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#node"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#message"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#severity"), DUMMY_VALUE,
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#languageIn"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#nodeKind"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#datatype"), DUMMY_VALUE,
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#minCount"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#maxCount"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#minLength"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#maxLength"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#minExclusive"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#maxExclusive"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#minInclusive"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#maxInclusive"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#pattern"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#class"), DUMMY_VALUE,
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#targetNode"), Values.bnode("bnode1"),
"Expected predicate <%s> to have a Literal or an IRI as object, but found BNode for _:bnode1 - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#targetClass"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#targetSubjectsOf"), DUMMY_VALUE,
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#targetObjectsOf"), DUMMY_VALUE,
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#deactivated"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#uniqueLang"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#closed"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#ignoredProperties"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#flags"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#path"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#in"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#equals"), DUMMY_VALUE,
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#disjoint"), DUMMY_VALUE,
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#lessThan"), DUMMY_VALUE,
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#lessThanOrEquals"), DUMMY_VALUE,
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#target"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#qualifiedValueShape"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#qualifiedValueShapesDisjoint"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#qualifiedMinCount"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#qualifiedMaxCount"), IRI,
"Expected predicate <%s> to have a Literal as object, but found IRI for http://example.org/iri - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://datashapes.org/dash#hasValueIn"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://rdf4j.org/shacl-extensions#targetShape"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#sparql"), DUMMY_VALUE,
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>")
);
}
}
Loading

0 comments on commit 0ead97f

Please sign in to comment.