From e255e768b2f06449c2a216bcea9c0dcb26cdd2f8 Mon Sep 17 00:00:00 2001 From: Dimitris Polissiou Date: Fri, 27 Sep 2024 14:50:17 +0300 Subject: [PATCH] Gather bean validation violations in a single field error Fix BeanValidationError path according to the spec --- .../validation/BeanValidationError.java | 46 ++++++++++++------- .../validation/BeanValidationUtil.java | 15 ++++-- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/server/implementation/src/main/java/io/smallrye/graphql/validation/BeanValidationError.java b/server/implementation/src/main/java/io/smallrye/graphql/validation/BeanValidationError.java index 2ea63e114..54c61636f 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/validation/BeanValidationError.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/validation/BeanValidationError.java @@ -6,6 +6,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; @@ -14,16 +16,21 @@ import graphql.ErrorClassification; import graphql.GraphQLError; -import graphql.language.NamedNode; +import graphql.execution.ResultPath; import graphql.language.SourceLocation; public class BeanValidationError implements GraphQLError { - private final ConstraintViolation violation; - private final List> requestedPath; + private final Set> violations; + private final ResultPath resultPath; + private final List sourceLocations; - public BeanValidationError(ConstraintViolation violation, List> requestedPath) { - this.violation = violation; - this.requestedPath = requestedPath; + public BeanValidationError( + Set> violations, + ResultPath resultPath, + List sourceLocations) { + this.violations = violations; + this.resultPath = resultPath; + this.sourceLocations = sourceLocations; } @Override @@ -33,28 +40,33 @@ public ErrorClassification getErrorType() { @Override public String getMessage() { - return "validation failed: " + violation.getPropertyPath() + " " + violation.getMessage(); + String joinedMessage = violations.stream() + .map(violation -> violation.getPropertyPath() + " " + violation.getMessage()) + .collect(Collectors.joining(", ")); + return "validation failed: " + joinedMessage; } @Override public List getLocations() { - return requestedPath.stream().map(NamedNode::getSourceLocation).collect(toList()); + return sourceLocations; } @Override public List getPath() { - return requestedPath.stream().map(argument -> (Object) argument.getName()).collect(toList()); + return resultPath.toList(); } @Override public Map getExtensions() { - Map extensions = new HashMap<>(); - extensions.put("violation.message", violation.getMessage()); - extensions.put("violation.propertyPath", - toStream(violation.getPropertyPath()).flatMap(this::items).collect(toList())); - extensions.put("violation.invalidValue", violation.getInvalidValue()); - extensions.put("violation.constraint", getConstraintAttributes()); - return extensions; + return Map.of("violations", violations.stream().map(this::getViolationAttributes).collect(toList())); + } + + private Map getViolationAttributes(ConstraintViolation violation) { + return Map.of( + "message", violation.getMessage(), + "propertyPath", toStream(violation.getPropertyPath()).flatMap(this::items).collect(toList()), + "invalidValue", violation.getInvalidValue(), + "constraint", getConstraintAttributes(violation)); } private Stream items(Path.Node node) { @@ -63,7 +75,7 @@ private Stream items(Path.Node node) { return Stream.of(node.getIndex().toString(), node.getName()); } - private Map getConstraintAttributes() { + private Map getConstraintAttributes(ConstraintViolation violation) { Map attributes = new HashMap<>(violation.getConstraintDescriptor().getAttributes()); attributes.computeIfPresent("groups", BeanValidationError::classNames); attributes.computeIfPresent("payload", BeanValidationError::classNames); diff --git a/server/implementation/src/main/java/io/smallrye/graphql/validation/BeanValidationUtil.java b/server/implementation/src/main/java/io/smallrye/graphql/validation/BeanValidationUtil.java index b701652ca..cb0007749 100644 --- a/server/implementation/src/main/java/io/smallrye/graphql/validation/BeanValidationUtil.java +++ b/server/implementation/src/main/java/io/smallrye/graphql/validation/BeanValidationUtil.java @@ -1,6 +1,7 @@ package io.smallrye.graphql.validation; import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; import java.lang.reflect.Method; import java.lang.reflect.Parameter; @@ -14,9 +15,11 @@ import org.eclipse.microprofile.graphql.Source; import graphql.execution.DataFetcherResult; +import graphql.execution.ResultPath; import graphql.language.Argument; import graphql.language.Field; import graphql.language.NamedNode; +import graphql.language.SourceLocation; import graphql.schema.DataFetchingEnvironment; import io.smallrye.graphql.api.Context; @@ -27,11 +30,15 @@ public static DataFetcherResult.Builder addConstraintViolationsToDataFet Method method, DataFetcherResult.Builder builder, DataFetchingEnvironment dfe) { + ResultPath resultPath = dfe.getExecutionStepInfo().getPath(); RequestNodeBuilder requestNodeBuilder = new RequestNodeBuilder(method, dfe); - violations.stream() - .map(violation -> new BeanValidationError(violation, requestNodeBuilder.build(violation))) - .forEach(builder::error); - return builder; + List sourceLocations = violations.stream() + .map(requestNodeBuilder::build) + .flatMap(List::stream) + .distinct() + .map(NamedNode::getSourceLocation) + .collect(toList()); + return builder.error(new BeanValidationError(violations, resultPath, sourceLocations)); } static class RequestNodeBuilder {