Skip to content

Commit 0ead97f

Browse files
authored
GH-4754 improve error handling in ShaclProperties class (#4755)
2 parents 7ad3ebf + 242da16 commit 0ead97f

8 files changed

Lines changed: 817 additions & 96 deletions

File tree

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/ShaclProperties.java

Lines changed: 346 additions & 84 deletions
Large diffs are not rendered by default.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
******************************************************************************/
11+
12+
package org.eclipse.rdf4j.sail.shacl.ast;
13+
14+
import org.eclipse.rdf4j.common.exception.RDF4JException;
15+
import org.eclipse.rdf4j.model.Resource;
16+
17+
/**
18+
* An exception indicating that something went wrong when parsing a shape. The id field contains the subject of the
19+
* shape statements.
20+
*/
21+
public class ShaclShapeParsingException extends RDF4JException {
22+
23+
Resource id;
24+
25+
public ShaclShapeParsingException(String msg, Resource id) {
26+
super(msg + " - Caused by shape with id: " + resourceToString(id));
27+
this.id = id;
28+
}
29+
30+
public ShaclShapeParsingException(String msg, Throwable t, Resource id) {
31+
super(msg + " - Caused by shape with id: " + resourceToString(id), t);
32+
this.id = id;
33+
}
34+
35+
public ShaclShapeParsingException(Throwable t, Resource id) {
36+
super(t.getMessage() + " - Caused by shape with id: " + resourceToString(id), t);
37+
this.id = id;
38+
}
39+
40+
public Resource getId() {
41+
return id;
42+
}
43+
44+
private static String resourceToString(Resource id) {
45+
assert id != null;
46+
if (id == null) {
47+
return "null";
48+
}
49+
if (id.isIRI()) {
50+
return "<" + id.stringValue() + ">";
51+
}
52+
if (id.isBNode()) {
53+
return id.toString();
54+
}
55+
if (id.isTriple()) {
56+
return "TRIPLE " + id;
57+
}
58+
return id.toString();
59+
}
60+
}

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/Shape.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,8 @@ List<ConstraintComponent> getConstraintComponents(ShaclProperties properties, Sh
274274
constraintComponent.add(new UniqueLangConstraintComponent());
275275
}
276276

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

658658
try (Stream<Resource> resources = shapeSourceWithContext.getTargetableShape()) {
659659
List<Shape> shapes = resources
660-
.map(r -> new ShaclProperties(r, shapeSourceWithContext))
660+
.map(r -> {
661+
try {
662+
return new ShaclProperties(r, shapeSourceWithContext);
663+
} catch (Exception e) {
664+
throw new ShaclShapeParsingException(e, r);
665+
}
666+
})
661667
.map(p -> {
662-
if (p.getType() == SHACL.NODE_SHAPE) {
663-
return NodeShape.getInstance(p, shapeSourceWithContext, parseSettings, cache);
664-
} else if (p.getType() == SHACL.PROPERTY_SHAPE) {
665-
return PropertyShape.getInstance(p, shapeSourceWithContext, parseSettings, cache);
668+
try {
669+
if (p.getType() == SHACL.NODE_SHAPE) {
670+
return NodeShape.getInstance(p, shapeSourceWithContext, parseSettings, cache);
671+
} else if (p.getType() == SHACL.PROPERTY_SHAPE) {
672+
return PropertyShape.getInstance(p, shapeSourceWithContext, parseSettings, cache);
673+
}
674+
throw new ShaclShapeParsingException("Unknown shape type", p.getId());
675+
} catch (Exception e) {
676+
if (e instanceof ShaclShapeParsingException) {
677+
throw e;
678+
}
679+
throw new ShaclShapeParsingException(e, p.getId());
666680
}
667-
throw new IllegalStateException("Unknown shape type for " + p.getId());
681+
668682
})
669683
.collect(Collectors.toList());
670684

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/QualifiedMaxCountConstraintComponent.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050

5151
public class QualifiedMaxCountConstraintComponent extends AbstractConstraintComponent {
5252
Shape qualifiedValueShape;
53-
Boolean qualifiedValueShapesDisjoint;
53+
boolean qualifiedValueShapesDisjoint;
5454
Long qualifiedMaxCount;
5555

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

87-
if (qualifiedValueShapesDisjoint != null) {
87+
if (qualifiedValueShapesDisjoint) {
8888
model.add(subject, SHACL.QUALIFIED_VALUE_SHAPES_DISJOINT, literal(qualifiedValueShapesDisjoint));
8989
}
9090

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/constraintcomponents/QualifiedMinCountConstraintComponent.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353

5454
public class QualifiedMinCountConstraintComponent extends AbstractConstraintComponent {
5555
Shape qualifiedValueShape;
56-
Boolean qualifiedValueShapesDisjoint;
56+
boolean qualifiedValueShapesDisjoint;
5757
Long qualifiedMinCount;
5858

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

90-
if (qualifiedValueShapesDisjoint != null) {
90+
if (qualifiedValueShapesDisjoint) {
9191
model.add(subject, SHACL.QUALIFIED_VALUE_SHAPES_DISJOINT, literal(qualifiedValueShapesDisjoint));
9292
}
9393

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
******************************************************************************/
11+
12+
package org.eclipse.rdf4j.sail.shacl.ast;
13+
14+
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
import static org.junit.jupiter.api.Assertions.assertThrows;
16+
import static org.mockito.ArgumentMatchers.any;
17+
import static org.mockito.Mockito.when;
18+
19+
import java.util.stream.Stream;
20+
21+
import org.eclipse.rdf4j.model.*;
22+
import org.eclipse.rdf4j.model.util.Values;
23+
import org.eclipse.rdf4j.sail.shacl.wrapper.shape.ShapeSource;
24+
import org.junit.jupiter.api.extension.ExtendWith;
25+
import org.junit.jupiter.params.ParameterizedTest;
26+
import org.junit.jupiter.params.provider.Arguments;
27+
import org.junit.jupiter.params.provider.MethodSource;
28+
import org.mockito.InjectMocks;
29+
import org.mockito.Mock;
30+
import org.mockito.junit.jupiter.MockitoExtension;
31+
32+
@ExtendWith(MockitoExtension.class)
33+
public class ShaclPropertiesCastingTest {
34+
35+
public static final Literal DUMMY_VALUE = Values.literal("dummy value");
36+
public static final IRI IRI = Values.iri("http://example.org/iri");
37+
@Mock
38+
private Statement statement;
39+
40+
@Mock
41+
private ShapeSource shapeSource;
42+
43+
@InjectMocks
44+
private ShaclProperties shaclProperties;
45+
46+
@ParameterizedTest
47+
@MethodSource("provideArgumentsForTest")
48+
public void testConstructor(IRI predicate, Value value, String exceptedMessage) {
49+
String exceptedMessageWithPredicate = String.format(exceptedMessage, predicate);
50+
51+
when(shapeSource.getAllStatements(any(Resource.class))).thenReturn(Stream.of(statement));
52+
when(statement.getPredicate()).thenReturn(predicate);
53+
when(statement.getObject()).thenReturn(value);
54+
55+
ShaclShapeParsingException exception = assertThrows(ShaclShapeParsingException.class,
56+
() -> new ShaclProperties(Values.iri("http://example.org/shape1"), shapeSource)
57+
);
58+
59+
assertEquals(exceptedMessageWithPredicate, exception.getMessage());
60+
61+
}
62+
63+
private static Stream<Arguments> provideArgumentsForTest() {
64+
return Stream.of(
65+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#or"), DUMMY_VALUE,
66+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
67+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#xone"), DUMMY_VALUE,
68+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
69+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#and"), DUMMY_VALUE,
70+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
71+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#not"), DUMMY_VALUE,
72+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
73+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#property"), DUMMY_VALUE,
74+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
75+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#node"), DUMMY_VALUE,
76+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
77+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#message"), IRI,
78+
"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>"),
79+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#severity"), DUMMY_VALUE,
80+
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
81+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#languageIn"), DUMMY_VALUE,
82+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
83+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#nodeKind"), DUMMY_VALUE,
84+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
85+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#datatype"), DUMMY_VALUE,
86+
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
87+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#minCount"), IRI,
88+
"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>"),
89+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#maxCount"), IRI,
90+
"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>"),
91+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#minLength"), IRI,
92+
"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>"),
93+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#maxLength"), IRI,
94+
"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>"),
95+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#minExclusive"), IRI,
96+
"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>"),
97+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#maxExclusive"), IRI,
98+
"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>"),
99+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#minInclusive"), IRI,
100+
"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>"),
101+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#maxInclusive"), IRI,
102+
"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>"),
103+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#pattern"), IRI,
104+
"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>"),
105+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#class"), DUMMY_VALUE,
106+
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
107+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#targetNode"), Values.bnode("bnode1"),
108+
"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>"),
109+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#targetClass"), DUMMY_VALUE,
110+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
111+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#targetSubjectsOf"), DUMMY_VALUE,
112+
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
113+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#targetObjectsOf"), DUMMY_VALUE,
114+
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
115+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#deactivated"), IRI,
116+
"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>"),
117+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#uniqueLang"), IRI,
118+
"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>"),
119+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#closed"), IRI,
120+
"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>"),
121+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#ignoredProperties"), DUMMY_VALUE,
122+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
123+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#flags"), IRI,
124+
"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>"),
125+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#path"), DUMMY_VALUE,
126+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
127+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#in"), DUMMY_VALUE,
128+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
129+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#equals"), DUMMY_VALUE,
130+
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
131+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#disjoint"), DUMMY_VALUE,
132+
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
133+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#lessThan"), DUMMY_VALUE,
134+
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
135+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#lessThanOrEquals"), DUMMY_VALUE,
136+
"Expected predicate <%s> to have an IRI as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
137+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#target"), DUMMY_VALUE,
138+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
139+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#qualifiedValueShape"), DUMMY_VALUE,
140+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
141+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#qualifiedValueShapesDisjoint"), IRI,
142+
"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>"),
143+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#qualifiedMinCount"), IRI,
144+
"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>"),
145+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#qualifiedMaxCount"), IRI,
146+
"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>"),
147+
Arguments.of(Values.iri("http://datashapes.org/dash#hasValueIn"), DUMMY_VALUE,
148+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
149+
Arguments.of(Values.iri("http://rdf4j.org/shacl-extensions#targetShape"), DUMMY_VALUE,
150+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>"),
151+
Arguments.of(Values.iri("http://www.w3.org/ns/shacl#sparql"), DUMMY_VALUE,
152+
"Expected predicate <%s> to have a Resource as object, but found Literal for \"dummy value\" - Caused by shape with id: <http://example.org/shape1>")
153+
);
154+
}
155+
}

0 commit comments

Comments
 (0)