Skip to content

Commit e255e76

Browse files
committed
Gather bean validation violations in a single field error
Fix BeanValidationError path according to the spec
1 parent 1f30a07 commit e255e76

File tree

2 files changed

+40
-21
lines changed

2 files changed

+40
-21
lines changed

server/implementation/src/main/java/io/smallrye/graphql/validation/BeanValidationError.java

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import java.util.HashMap;
77
import java.util.List;
88
import java.util.Map;
9+
import java.util.Set;
10+
import java.util.stream.Collectors;
911
import java.util.stream.Stream;
1012
import java.util.stream.StreamSupport;
1113

@@ -14,16 +16,21 @@
1416

1517
import graphql.ErrorClassification;
1618
import graphql.GraphQLError;
17-
import graphql.language.NamedNode;
19+
import graphql.execution.ResultPath;
1820
import graphql.language.SourceLocation;
1921

2022
public class BeanValidationError implements GraphQLError {
21-
private final ConstraintViolation<?> violation;
22-
private final List<NamedNode<?>> requestedPath;
23+
private final Set<ConstraintViolation<?>> violations;
24+
private final ResultPath resultPath;
25+
private final List<SourceLocation> sourceLocations;
2326

24-
public BeanValidationError(ConstraintViolation<?> violation, List<NamedNode<?>> requestedPath) {
25-
this.violation = violation;
26-
this.requestedPath = requestedPath;
27+
public BeanValidationError(
28+
Set<ConstraintViolation<?>> violations,
29+
ResultPath resultPath,
30+
List<SourceLocation> sourceLocations) {
31+
this.violations = violations;
32+
this.resultPath = resultPath;
33+
this.sourceLocations = sourceLocations;
2734
}
2835

2936
@Override
@@ -33,28 +40,33 @@ public ErrorClassification getErrorType() {
3340

3441
@Override
3542
public String getMessage() {
36-
return "validation failed: " + violation.getPropertyPath() + " " + violation.getMessage();
43+
String joinedMessage = violations.stream()
44+
.map(violation -> violation.getPropertyPath() + " " + violation.getMessage())
45+
.collect(Collectors.joining(", "));
46+
return "validation failed: " + joinedMessage;
3747
}
3848

3949
@Override
4050
public List<SourceLocation> getLocations() {
41-
return requestedPath.stream().map(NamedNode::getSourceLocation).collect(toList());
51+
return sourceLocations;
4252
}
4353

4454
@Override
4555
public List<Object> getPath() {
46-
return requestedPath.stream().map(argument -> (Object) argument.getName()).collect(toList());
56+
return resultPath.toList();
4757
}
4858

4959
@Override
5060
public Map<String, Object> getExtensions() {
51-
Map<String, Object> extensions = new HashMap<>();
52-
extensions.put("violation.message", violation.getMessage());
53-
extensions.put("violation.propertyPath",
54-
toStream(violation.getPropertyPath()).flatMap(this::items).collect(toList()));
55-
extensions.put("violation.invalidValue", violation.getInvalidValue());
56-
extensions.put("violation.constraint", getConstraintAttributes());
57-
return extensions;
61+
return Map.of("violations", violations.stream().map(this::getViolationAttributes).collect(toList()));
62+
}
63+
64+
private Map<String, Object> getViolationAttributes(ConstraintViolation<?> violation) {
65+
return Map.of(
66+
"message", violation.getMessage(),
67+
"propertyPath", toStream(violation.getPropertyPath()).flatMap(this::items).collect(toList()),
68+
"invalidValue", violation.getInvalidValue(),
69+
"constraint", getConstraintAttributes(violation));
5870
}
5971

6072
private Stream<String> items(Path.Node node) {
@@ -63,7 +75,7 @@ private Stream<String> items(Path.Node node) {
6375
return Stream.of(node.getIndex().toString(), node.getName());
6476
}
6577

66-
private Map<String, Object> getConstraintAttributes() {
78+
private Map<String, Object> getConstraintAttributes(ConstraintViolation<?> violation) {
6779
Map<String, Object> attributes = new HashMap<>(violation.getConstraintDescriptor().getAttributes());
6880
attributes.computeIfPresent("groups", BeanValidationError::classNames);
6981
attributes.computeIfPresent("payload", BeanValidationError::classNames);

server/implementation/src/main/java/io/smallrye/graphql/validation/BeanValidationUtil.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.smallrye.graphql.validation;
22

33
import static java.util.Arrays.asList;
4+
import static java.util.stream.Collectors.toList;
45

56
import java.lang.reflect.Method;
67
import java.lang.reflect.Parameter;
@@ -14,9 +15,11 @@
1415
import org.eclipse.microprofile.graphql.Source;
1516

1617
import graphql.execution.DataFetcherResult;
18+
import graphql.execution.ResultPath;
1719
import graphql.language.Argument;
1820
import graphql.language.Field;
1921
import graphql.language.NamedNode;
22+
import graphql.language.SourceLocation;
2023
import graphql.schema.DataFetchingEnvironment;
2124
import io.smallrye.graphql.api.Context;
2225

@@ -27,11 +30,15 @@ public static DataFetcherResult.Builder<Object> addConstraintViolationsToDataFet
2730
Method method,
2831
DataFetcherResult.Builder<Object> builder,
2932
DataFetchingEnvironment dfe) {
33+
ResultPath resultPath = dfe.getExecutionStepInfo().getPath();
3034
RequestNodeBuilder requestNodeBuilder = new RequestNodeBuilder(method, dfe);
31-
violations.stream()
32-
.map(violation -> new BeanValidationError(violation, requestNodeBuilder.build(violation)))
33-
.forEach(builder::error);
34-
return builder;
35+
List<SourceLocation> sourceLocations = violations.stream()
36+
.map(requestNodeBuilder::build)
37+
.flatMap(List::stream)
38+
.distinct()
39+
.map(NamedNode::getSourceLocation)
40+
.collect(toList());
41+
return builder.error(new BeanValidationError(violations, resultPath, sourceLocations));
3542
}
3643

3744
static class RequestNodeBuilder {

0 commit comments

Comments
 (0)