From 946dc477b0b1e0387160d12f4b03d7151a2e5cf2 Mon Sep 17 00:00:00 2001 From: bbaker Date: Thu, 13 Feb 2025 12:52:23 +1100 Subject: [PATCH 1/2] Added the ability to parse our ExecutionResult from a map that came from toSpecification --- src/main/java/graphql/ExecutionResult.java | 10 +++ .../java/graphql/ExecutionResultImpl.java | 18 +++++ src/main/java/graphql/GraphQLError.java | 11 +++ src/main/java/graphql/GraphqlErrorHelper.java | 68 +++++++++++++++++-- .../graphql/ExecutionResultImplTest.groovy | 41 +++++++++-- .../graphql/GraphqlErrorHelperTest.groovy | 48 +++++++++++++ 6 files changed, 185 insertions(+), 11 deletions(-) diff --git a/src/main/java/graphql/ExecutionResult.java b/src/main/java/graphql/ExecutionResult.java index 4870144fe5..f2e94765bb 100644 --- a/src/main/java/graphql/ExecutionResult.java +++ b/src/main/java/graphql/ExecutionResult.java @@ -56,6 +56,16 @@ public interface ExecutionResult { */ Map toSpecification(); + /** + * This allows you to turn a map of results from {@link #toSpecification()} and turn it back into a {@link ExecutionResult} + * + * @param specificationMap the specification result map + * + * @return a new {@link ExecutionResult} from that map + */ + static ExecutionResult fromSpecification(Map specificationMap) { + return ExecutionResultImpl.fromSpecification(specificationMap); + } /** * This helps you transform the current {@link ExecutionResult} object into another one by starting a builder with all diff --git a/src/main/java/graphql/ExecutionResultImpl.java b/src/main/java/graphql/ExecutionResultImpl.java index 62419a63a7..d39dbda157 100644 --- a/src/main/java/graphql/ExecutionResultImpl.java +++ b/src/main/java/graphql/ExecutionResultImpl.java @@ -97,6 +97,24 @@ private Object errorsToSpec(List errors) { return map(errors, GraphQLError::toSpecification); } + @SuppressWarnings("unchecked") + static ExecutionResult fromSpecification(Map specificationMap) { + ExecutionResult.Builder builder = ExecutionResult.newExecutionResult(); + Object data = specificationMap.get("data"); + if (data != null) { + builder.data(data); + } + List> errors = (List>) specificationMap.get("errors"); + if (errors != null) { + builder.errors(GraphqlErrorHelper.fromSpecification(errors)); + } + Map extensions = (Map) specificationMap.get("extensions"); + if (extensions != null) { + builder.extensions(extensions); + } + return builder.build(); + } + @Override public String toString() { return "ExecutionResultImpl{" + diff --git a/src/main/java/graphql/GraphQLError.java b/src/main/java/graphql/GraphQLError.java index c18752b14a..7079d7579a 100644 --- a/src/main/java/graphql/GraphQLError.java +++ b/src/main/java/graphql/GraphQLError.java @@ -70,6 +70,17 @@ default Map getExtensions() { return null; } + /** + * This can be called to turn a specification error map into {@link GraphQLError} + * + * @param specificationMap the map of values that should have come via {@link GraphQLError#toSpecification()} + * + * @return a {@link GraphQLError} + */ + static GraphQLError fromSpecification(Map specificationMap) { + return GraphqlErrorHelper.fromSpecification(specificationMap); + } + /** * @return a new builder of {@link GraphQLError}s */ diff --git a/src/main/java/graphql/GraphqlErrorHelper.java b/src/main/java/graphql/GraphqlErrorHelper.java index f613547565..8dd8773d47 100644 --- a/src/main/java/graphql/GraphqlErrorHelper.java +++ b/src/main/java/graphql/GraphqlErrorHelper.java @@ -2,10 +2,12 @@ import graphql.language.SourceLocation; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; import static graphql.collect.ImmutableKit.mapAndDropNulls; @@ -13,7 +15,7 @@ * This little helper allows GraphQlErrors to implement * common things (hashcode/ equals ) and to specification more easily */ -@SuppressWarnings("SimplifiableIfStatement") +@SuppressWarnings({"SimplifiableIfStatement", "unchecked"}) @Internal public class GraphqlErrorHelper { @@ -55,11 +57,12 @@ public static Object locations(List locations) { } /** - * Positive integers starting from 1 required for error locations, - * from the spec ... + * Positive integers starting from 1 required for error locations, + * from the spec ... * * @param location the source location in play - * @return a value for source location of the error + * + * @return a value for source location of the error */ public static Object location(SourceLocation location) { int line = location.getLine(); @@ -70,6 +73,59 @@ public static Object location(SourceLocation location) { return Map.of("line", line, "column", column); } + static List fromSpecification(List> specificationMaps) { + return specificationMaps.stream() + .map(GraphqlErrorHelper::fromSpecification).collect(Collectors.toList()); + } + + static GraphQLError fromSpecification(Map specificationMap) { + GraphQLError.Builder errorBuilder = GraphQLError.newError(); + // builder will enforce not null message + errorBuilder.message((String) specificationMap.get("message")); + extractLocations(errorBuilder, specificationMap); + extractPath(errorBuilder, specificationMap); + extractExtensions(errorBuilder, specificationMap); + return errorBuilder.build(); + } + + private static void extractPath(GraphQLError.Builder errorBuilder, Map rawError) { + List path = (List) rawError.get("path"); + if (path != null) { + errorBuilder.path(path); + } + } + + private static void extractExtensions(GraphQLError.Builder errorBuilder, Map rawError) { + Map extensions = (Map) rawError.get("extensions"); + if (extensions != null) { + errorBuilder.extensions(extensions); + Object classification = extensions.get("classification"); + if (classification != null) { + ErrorClassification errorClassification = ErrorClassification.errorClassification((String) classification); + errorBuilder.errorType(errorClassification); + } + } + + } + + private static void extractLocations(GraphQLError.Builder errorBuilder, Map rawError) { + List locations = (List) rawError.get("locations"); + if (locations != null) { + List sourceLocations = new ArrayList<>(); + for (Object locationObj : locations) { + Map location = (Map) locationObj; + if (location != null) { + Integer line = (Integer) location.get("line"); + Integer column = (Integer) location.get("column"); + if (line != null && column != null) { + sourceLocations.add(new SourceLocation(line, column)); + } + } + } + errorBuilder.locations(sourceLocations); + } + } + public static int hashCode(GraphQLError dis) { int result = 1; result = 31 * result + Objects.hashCode(dis.getMessage()); @@ -83,7 +139,9 @@ public static boolean equals(GraphQLError dis, Object o) { if (dis == o) { return true; } - if (o == null || dis.getClass() != o.getClass()) return false; + if (o == null || dis.getClass() != o.getClass()) { + return false; + } GraphQLError dat = (GraphQLError) o; diff --git a/src/test/groovy/graphql/ExecutionResultImplTest.groovy b/src/test/groovy/graphql/ExecutionResultImplTest.groovy index a1bb59e2a5..8b8894cca3 100644 --- a/src/test/groovy/graphql/ExecutionResultImplTest.groovy +++ b/src/test/groovy/graphql/ExecutionResultImplTest.groovy @@ -176,29 +176,58 @@ class ExecutionResultImplTest extends Specification { def "test setting extensions"() { given: - def startEr = new ExecutionResultImpl("Some Data", KNOWN_ERRORS,null) + def startEr = new ExecutionResultImpl("Some Data", KNOWN_ERRORS, null) - def er = ExecutionResultImpl.newExecutionResult().from(startEr).extensions([ext1:"here"]).build() + def er = ExecutionResultImpl.newExecutionResult().from(startEr).extensions([ext1: "here"]).build() when: def extensions = er.getExtensions() then: - extensions == [ext1:"here"] + extensions == [ext1: "here"] } def "test adding extension"() { given: - def startEr = new ExecutionResultImpl("Some Data", KNOWN_ERRORS,[ext1:"here"]) + def startEr = new ExecutionResultImpl("Some Data", KNOWN_ERRORS, [ext1: "here"]) - def er = ExecutionResultImpl.newExecutionResult().from(startEr).addExtension("ext2","aswell").build() + def er = ExecutionResultImpl.newExecutionResult().from(startEr).addExtension("ext2", "aswell").build() when: def extensions = er.getExtensions() then: - extensions == [ext1:"here", ext2 : "aswell"] + extensions == [ext1: "here", ext2: "aswell"] } + def "can parse out a map of to an ER"() { + when: + def map = [data: [f: "v"]] + def er = ExecutionResult.fromSpecification(map) + then: + er.data == [f: "v"] + er.extensions == null + er.errors.isEmpty() + + when: + // GraphqlErrorHelperTest is more extensive tests for error parsing which we will not repeat here + map = [errors: [[message: "m0"], [message: "m1"]]] + er = ExecutionResult.fromSpecification(map) + then: + er.data == null + er.extensions == null + !er.errors.isEmpty() + er.errors[0].message == "m0" + er.errors[1].message == "m1" + + when: + map = [data: [f: "v"], extensions: [ext1: "here", ext2: "and here"]] + er = ExecutionResult.fromSpecification(map) + then: + er.data == [f: "v"] + er.extensions == [ext1: "here", ext2: "and here"] + er.errors.isEmpty() + + } } diff --git a/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy b/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy index 88bfecb444..a8f76c82ab 100644 --- a/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy +++ b/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy @@ -106,4 +106,52 @@ class GraphqlErrorHelperTest extends Specification { message : "has extensions" ] } + + def "can parse out a map and make an error"() { + when: + def rawError = [message: "m"] + def graphQLError = GraphqlErrorHelper.fromSpecification(rawError) + then: + graphQLError.getMessage() == "m" + graphQLError.getErrorType() == ErrorType.DataFetchingException // default from error builder + graphQLError.getLocations() == [] + graphQLError.getPath() == null + graphQLError.getExtensions() == null + + when: + rawError = [message: "m"] + graphQLError = GraphQLError.fromSpecification(rawError) // just so we reference the public method + then: + graphQLError.getMessage() == "m" + graphQLError.getErrorType() == ErrorType.DataFetchingException // default from error builder + graphQLError.getLocations() == [] + graphQLError.getPath() == null + graphQLError.getExtensions() == null + + when: + def extensionsMap = [attr1: "a1", attr2: "a2", classification: "CLASSIFICATION-X"] + rawError = [message: "m", path: ["a", "b"], locations: [[line: 2, column: 3]], extensions: extensionsMap] + graphQLError = GraphqlErrorHelper.fromSpecification(rawError) + + then: + graphQLError.getMessage() == "m" + graphQLError.getErrorType().toString() == "CLASSIFICATION-X" + graphQLError.getLocations() == [new SourceLocation(2, 3)] + graphQLError.getPath() == ["a", "b"] + graphQLError.getExtensions() == extensionsMap + + + when: "can do a list of errors" + def rawErrors = [[message: "m0"], [message: "m1"]] + def errors = GraphqlErrorHelper.fromSpecification(rawErrors) + then: + errors.size() == 2 + errors.eachWithIndex { GraphQLError gErr, int i -> + assert gErr.getMessage() == "m" + i + assert gErr.getErrorType() == ErrorType.DataFetchingException // default from error builder + assert gErr.getLocations() == [] + assert gErr.getPath() == null + assert graphQLError.getExtensions() == null + } + } } From 9c04196a7e93503ea5745a56e91e304164030af0 Mon Sep 17 00:00:00 2001 From: bbaker Date: Thu, 13 Feb 2025 13:30:16 +1100 Subject: [PATCH 2/2] Added the ability to parse our ExecutionResult from a map that came from toSpecification - test fix up --- src/test/groovy/graphql/GraphqlErrorHelperTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy b/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy index a8f76c82ab..018c9f1577 100644 --- a/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy +++ b/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy @@ -151,7 +151,7 @@ class GraphqlErrorHelperTest extends Specification { assert gErr.getErrorType() == ErrorType.DataFetchingException // default from error builder assert gErr.getLocations() == [] assert gErr.getPath() == null - assert graphQLError.getExtensions() == null + assert gErr.getExtensions() == null } } }