Skip to content

Commit c0ef995

Browse files
mrdon-atlassianbbakerman
authored andcommitted
First cut at supporting errors from data fetchers
1 parent c0443da commit c0ef995

File tree

4 files changed

+221
-0
lines changed

4 files changed

+221
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package graphql.execution;
2+
3+
import graphql.GraphQLError;
4+
import graphql.schema.DataFetcher;
5+
6+
import java.util.List;
7+
8+
import static java.util.Objects.requireNonNull;
9+
10+
11+
/**
12+
* An object that can be returned from a {@link DataFetcher} that contains both data and errors to be relativized and
13+
* added to the final result.
14+
*
15+
* @param <T> The type of the data fetched
16+
*/
17+
public class DataFetcherResult<T> {
18+
19+
private final T data;
20+
private final List<GraphQLError> errors;
21+
22+
public DataFetcherResult(T data, List<GraphQLError> errors) {
23+
this.data = data;
24+
this.errors = requireNonNull(errors);
25+
}
26+
27+
/**
28+
* @return The data fetched. May be null
29+
*/
30+
public T getData() {
31+
return data;
32+
}
33+
34+
/**
35+
* @return errors encountered when fetching data. Must not be null.
36+
*/
37+
public List<GraphQLError> getErrors() {
38+
return errors;
39+
}
40+
}

src/main/java/graphql/execution/ExecutionStrategy.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package graphql.execution;
22

3+
import graphql.ErrorType;
34
import graphql.ExecutionResult;
45
import graphql.ExecutionResultImpl;
6+
import graphql.GraphQLError;
57
import graphql.PublicSpi;
68
import graphql.SerializationError;
79
import graphql.TypeResolutionEnvironment;
@@ -12,6 +14,7 @@
1214
import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters;
1315
import graphql.introspection.Introspection;
1416
import graphql.language.Field;
17+
import graphql.language.SourceLocation;
1518
import graphql.schema.CoercingSerializeException;
1619
import graphql.schema.DataFetcher;
1720
import graphql.schema.DataFetchingEnvironment;
@@ -37,6 +40,7 @@
3740
import java.util.Optional;
3841
import java.util.concurrent.CompletableFuture;
3942
import java.util.concurrent.CompletionException;
43+
import java.util.function.Function;
4044
import java.util.stream.IntStream;
4145

4246
import static graphql.execution.ExecutionTypeInfo.newTypeInfo;
@@ -328,6 +332,15 @@ protected CompletableFuture<ExecutionResult> completeValue(ExecutionContext exec
328332
Object result = unboxPossibleOptional(parameters.source());
329333
GraphQLType fieldType = typeInfo.getType();
330334

335+
if (result != null && result instanceof DataFetcherResult) {
336+
//noinspection unchecked
337+
DataFetcherResult<Object> dataFetcherResult = (DataFetcherResult)result;
338+
result = dataFetcherResult.getData();
339+
dataFetcherResult.getErrors().stream()
340+
.map(relError -> new AbsoluteGraphQLError(parameters, relError))
341+
.forEach(e -> executionContext.addError(e, ExecutionPath.fromList(e.getPath())));
342+
}
343+
331344
if (result == null) {
332345
return completedFuture(new ExecutionResultImpl(parameters.nonNullFieldValidator().validate(parameters.path(), null), null));
333346
} else if (fieldType instanceof GraphQLList) {
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package graphql.execution;
2+
3+
import graphql.ErrorType;
4+
import graphql.GraphQLError;
5+
import graphql.language.SourceLocation;
6+
import graphql.schema.DataFetcher;
7+
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
import java.util.Optional;
11+
import java.util.stream.Collectors;
12+
13+
import static com.sun.tools.javac.util.Assert.checkNonNull;
14+
15+
/**
16+
* A {@link GraphQLError} that has been changed from a {@link DataFetcher} relative error to an absolute one.
17+
*/
18+
class AbsoluteGraphQLError implements GraphQLError {
19+
20+
private final List<SourceLocation> locations;
21+
private final List<Object> absolutePath;
22+
private final GraphQLError relativeError;
23+
24+
AbsoluteGraphQLError(ExecutionStrategyParameters executionStrategyParameters, GraphQLError relativeError) {
25+
checkNonNull(executionStrategyParameters);
26+
this.relativeError = checkNonNull(relativeError);
27+
List<Object> path = new ArrayList<>();
28+
path.addAll(executionStrategyParameters.path().toList());
29+
path.addAll(relativeError.getPath());
30+
this.absolutePath = path;
31+
32+
Optional<SourceLocation> baseLocation;
33+
if (!executionStrategyParameters.field().isEmpty()) {
34+
baseLocation = Optional.of(executionStrategyParameters.field().get(0).getSourceLocation());
35+
} else {
36+
baseLocation = Optional.empty();
37+
}
38+
39+
this.locations = Optional.ofNullable(
40+
relativeError.getLocations())
41+
.map(locations -> locations.stream()
42+
.map(l ->
43+
baseLocation
44+
.map(base -> new SourceLocation(base.getLine() + l.getLine(), base.getColumn() + l.getColumn()))
45+
.orElse(null))
46+
.collect(Collectors.toList()))
47+
.orElse(null);
48+
}
49+
50+
@Override
51+
public String getMessage() {
52+
return relativeError.getMessage();
53+
}
54+
55+
@Override
56+
public List<SourceLocation> getLocations() {
57+
return locations;
58+
}
59+
60+
@Override
61+
public ErrorType getErrorType() {
62+
return relativeError.getErrorType();
63+
}
64+
65+
@Override
66+
public List<Object> getPath() {
67+
return absolutePath;
68+
}
69+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import graphql.ErrorType;
2+
import graphql.ExecutionResult;
3+
import graphql.GraphQL;
4+
import graphql.GraphQLError;
5+
import graphql.execution.DataFetcherResult;
6+
import graphql.language.SourceLocation;
7+
import graphql.schema.DataFetcher;
8+
import graphql.schema.DataFetchingEnvironment;
9+
import graphql.schema.GraphQLSchema;
10+
import graphql.schema.StaticDataFetcher;
11+
import graphql.schema.idl.RuntimeWiring;
12+
import graphql.schema.idl.SchemaGenerator;
13+
import graphql.schema.idl.SchemaParser;
14+
import graphql.schema.idl.TypeDefinitionRegistry;
15+
import org.junit.Test;
16+
17+
import java.util.Arrays;
18+
import java.util.HashMap;
19+
import java.util.List;
20+
import java.util.Map;
21+
22+
import static graphql.schema.idl.RuntimeWiring.newRuntimeWiring;
23+
import static java.util.Collections.emptyList;
24+
import static java.util.Collections.emptyMap;
25+
import static java.util.Collections.singletonList;
26+
import static java.util.Collections.singletonMap;
27+
import static org.junit.Assert.assertEquals;
28+
import static org.junit.Assert.assertTrue;
29+
30+
31+
public class DataFetcherWithErrorsTest {
32+
33+
@Test
34+
public void testDataFetcherWithErrors() {
35+
GraphQLError error = new GraphQLError() {
36+
37+
@Override
38+
public String getMessage() {
39+
return "badField is bad";
40+
}
41+
42+
@Override
43+
public List<SourceLocation> getLocations() {
44+
return singletonList(new SourceLocation(2, 10));
45+
}
46+
47+
@Override
48+
public ErrorType getErrorType() {
49+
return ErrorType.DataFetchingException;
50+
}
51+
52+
@Override
53+
public List<Object> getPath() {
54+
return Arrays.asList("child", "badField");
55+
}
56+
};
57+
DataFetcher dataFetcher = environment -> new DataFetcherResult<Map>(
58+
singletonMap("child",
59+
new HashMap<String, Object>() {{
60+
put("goodField", "good");
61+
put("badField", null);
62+
}}
63+
),
64+
singletonList(error)
65+
);
66+
67+
String schema = "type Query{root: Root}\n" +
68+
"type Root{ parent: Parent}\n" +
69+
"type Parent{ child: Child}\n" +
70+
"type Child{\n" +
71+
" goodField: String,\n" +
72+
" badField: String\n" +
73+
"}";
74+
SchemaParser schemaParser = new SchemaParser();
75+
TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema);
76+
RuntimeWiring runtimeWiring = newRuntimeWiring()
77+
.type("Root", builder -> builder.dataFetcher("parent", dataFetcher))
78+
.type("Query", builder -> builder.dataFetcher("root", new StaticDataFetcher(emptyMap())))
79+
.build();
80+
81+
SchemaGenerator schemaGenerator = new SchemaGenerator();
82+
GraphQLSchema graphQLSchema = schemaGenerator.makeExecutableSchema(typeDefinitionRegistry, runtimeWiring);
83+
84+
GraphQL build = GraphQL.newGraphQL(graphQLSchema).build();
85+
ExecutionResult executionResult = build.execute("{root{parent{child{goodField, badField}}}}");
86+
System.out.println(executionResult.getData().toString());
87+
88+
assertEquals(1, executionResult.getErrors().size());
89+
GraphQLError firstError = executionResult.getErrors().get(0);
90+
assertEquals(Arrays.asList("root", "parent", "child", "badField"), firstError.getPath());
91+
assertEquals("badField is bad", firstError.getMessage());
92+
assertEquals(singletonList(new SourceLocation(3, 17)), firstError.getLocations());
93+
94+
Map<String, String> child = ((Map<String, Map<String, Map<String, Map<String, String>>>>) executionResult.getData()).get("root").get("parent").get("child");
95+
assertEquals("good", child.get("goodField"));
96+
assertEquals(null, child.get("badField"));
97+
98+
}
99+
}

0 commit comments

Comments
 (0)