Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/main/java/graphql/ExecutionResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ public interface ExecutionResult {
*/
Map<String, Object> 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<String, Object> specificationMap) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming is the opposite of toSpecification

return ExecutionResultImpl.fromSpecification(specificationMap);
}

/**
* This helps you transform the current {@link ExecutionResult} object into another one by starting a builder with all
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/graphql/ExecutionResultImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,24 @@ private Object errorsToSpec(List<GraphQLError> errors) {
return map(errors, GraphQLError::toSpecification);
}

@SuppressWarnings("unchecked")
static ExecutionResult fromSpecification(Map<String, Object> specificationMap) {
ExecutionResult.Builder<?> builder = ExecutionResult.newExecutionResult();
Object data = specificationMap.get("data");
if (data != null) {
builder.data(data);
}
List<Map<String, Object>> errors = (List<Map<String, Object>>) specificationMap.get("errors");
if (errors != null) {
builder.errors(GraphqlErrorHelper.fromSpecification(errors));
}
Map<Object, Object> extensions = (Map<Object, Object>) specificationMap.get("extensions");
if (extensions != null) {
builder.extensions(extensions);
}
return builder.build();
}

@Override
public String toString() {
return "ExecutionResultImpl{" +
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/graphql/GraphQLError.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ default Map<String, Object> 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<String, Object> specificationMap) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming is the opposite of toSpecification

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

impl in internal class

return GraphqlErrorHelper.fromSpecification(specificationMap);
}

/**
* @return a new builder of {@link GraphQLError}s
*/
Expand Down
68 changes: 63 additions & 5 deletions src/main/java/graphql/GraphqlErrorHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@

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;

/**
* 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 {

Expand Down Expand Up @@ -55,11 +57,12 @@ public static Object locations(List<SourceLocation> locations) {
}

/**
* Positive integers starting from 1 required for error locations,
* from the spec <a href="https://spec.graphql.org/draft/#sec-Errors.Error-Result-Format">...</a>
* Positive integers starting from 1 required for error locations,
* from the spec <a href="https://spec.graphql.org/draft/#sec-Errors.Error-Result-Format">...</a>
*
* @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();
Expand All @@ -70,6 +73,59 @@ public static Object location(SourceLocation location) {
return Map.of("line", line, "column", column);
}

static List<GraphQLError> fromSpecification(List<Map<String, Object>> specificationMaps) {
return specificationMaps.stream()
.map(GraphqlErrorHelper::fromSpecification).collect(Collectors.toList());
}

static GraphQLError fromSpecification(Map<String, Object> 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<String, Object> rawError) {
List<Object> path = (List<Object>) rawError.get("path");
if (path != null) {
errorBuilder.path(path);
}
}

private static void extractExtensions(GraphQLError.Builder<?> errorBuilder, Map<String, Object> rawError) {
Map<String, Object> extensions = (Map<String, Object>) 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<String, Object> rawError) {
List<Object> locations = (List<Object>) rawError.get("locations");
if (locations != null) {
List<SourceLocation> sourceLocations = new ArrayList<>();
for (Object locationObj : locations) {
Map<String, Object> location = (Map<String, Object>) 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());
Expand All @@ -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;

Expand Down
41 changes: 35 additions & 6 deletions src/test/groovy/graphql/ExecutionResultImplTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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()

}
}
48 changes: 48 additions & 0 deletions src/test/groovy/graphql/GraphqlErrorHelperTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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 gErr.getExtensions() == null
}
}
}
Loading