Skip to content

Commit 5d85e48

Browse files
authored
Merge pull request #3965 from graphql-java/24.x-cherry-pick-datafetcherresult
Cherry pick DataFetcherResult helpers
2 parents cc8d169 + e1a2926 commit 5d85e48

14 files changed

Lines changed: 159 additions & 38 deletions

src/main/java/graphql/execution/DataFetcherResult.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.ArrayList;
1111
import java.util.List;
1212
import java.util.Map;
13+
import java.util.Objects;
1314
import java.util.function.Consumer;
1415
import java.util.function.Function;
1516

@@ -24,10 +25,19 @@
2425
* This also allows you to pass down new local context objects between parent and child fields. If you return a
2526
* {@link #getLocalContext()} value then it will be passed down into any child fields via
2627
* {@link graphql.schema.DataFetchingEnvironment#getLocalContext()}
27-
*
28+
* <p>
2829
* You can also have {@link DataFetcher}s contribute to the {@link ExecutionResult#getExtensions()} by returning
2930
* extensions maps that will be merged together via the {@link graphql.extensions.ExtensionsBuilder} and its {@link graphql.extensions.ExtensionsMerger}
3031
* in place.
32+
* <p>
33+
* This provides {@link #hashCode()} and {@link #equals(Object)} methods that afford comparison with other {@link DataFetcherResult} object.s
34+
* However, to function correctly, this relies on the values provided in the following fields in turn also implementing {@link #hashCode()}} and {@link #equals(Object)} as appropriate:
35+
* <ul>
36+
* <li>The data returned in {@link #getData()}.
37+
* <li>The individual errors returned in {@link #getErrors()}.
38+
* <li>The context returned in {@link #getLocalContext()}.
39+
* <li>The keys/values in the {@link #getExtensions()} {@link Map}.
40+
* </ul>
3141
*
3242
* @param <T> The type of the data fetched
3343
*/
@@ -123,6 +133,35 @@ public <R> DataFetcherResult<R> map(Function<T, R> transformation) {
123133
.build();
124134
}
125135

136+
137+
@Override
138+
public boolean equals(Object o) {
139+
if (o == null || getClass() != o.getClass()) {
140+
return false;
141+
}
142+
143+
DataFetcherResult<?> that = (DataFetcherResult<?>) o;
144+
return Objects.equals(data, that.data)
145+
&& errors.equals(that.errors)
146+
&& Objects.equals(localContext, that.localContext)
147+
&& Objects.equals(extensions, that.extensions);
148+
}
149+
150+
@Override
151+
public int hashCode() {
152+
return Objects.hash(data, errors, localContext, extensions);
153+
}
154+
155+
@Override
156+
public String toString() {
157+
return "DataFetcherResult{" +
158+
"data=" + data +
159+
", errors=" + errors +
160+
", localContext=" + localContext +
161+
", extensions=" + extensions +
162+
'}';
163+
}
164+
126165
/**
127166
* Creates a new data fetcher result builder
128167
*

src/test/groovy/graphql/execution/DataFetcherResultTest.groovy

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package graphql.execution
22

3+
import graphql.GraphQLError
34
import graphql.InvalidSyntaxError
45
import graphql.validation.ValidationError
56
import graphql.validation.ValidationErrorType
@@ -107,4 +108,77 @@ class DataFetcherResultTest extends Specification {
107108
result.getExtensions() == [a : "b"]
108109
result.getErrors() == [error1, error2]
109110
}
111+
112+
def "implements equals/hashCode for matching results"() {
113+
when:
114+
def firstResult = toDataFetcherResult(first)
115+
def secondResult = toDataFetcherResult(second)
116+
117+
then:
118+
firstResult == secondResult
119+
firstResult.hashCode() == secondResult.hashCode()
120+
121+
where:
122+
first | second
123+
[data: "A string"] | [data: "A string"]
124+
[data: 5] | [data: 5]
125+
[data: ["a", "b"]] | [data: ["a", "b"]]
126+
[errors: [error("An error")]] | [errors: [error("An error")]]
127+
[data: "A value", errors: [error("An error")]] | [data: "A value", errors: [error("An error")]]
128+
[data: "A value", localContext: 5] | [data: "A value", localContext: 5]
129+
[data: "A value", errors: [error("An error")], localContext: 5] | [data: "A value", errors: [error("An error")], localContext: 5]
130+
[data: "A value", extensions: ["key": "value"]] | [data: "A value", extensions: ["key": "value"]]
131+
[data: "A value", errors: [error("An error")], localContext: 5, extensions: ["key": "value"]] | [data: "A value", errors: [error("An error")], localContext: 5, extensions: ["key": "value"]]
132+
}
133+
134+
def "implements equals/hashCode for different results"() {
135+
when:
136+
def firstResult = toDataFetcherResult(first)
137+
def secondResult = toDataFetcherResult(second)
138+
139+
then:
140+
firstResult != secondResult
141+
firstResult.hashCode() != secondResult.hashCode()
142+
143+
where:
144+
first | second
145+
[data: "A string"] | [data: "A different string"]
146+
[data: 5] | [data: "not 5"]
147+
[data: ["a", "b"]] | [data: ["a", "c"]]
148+
[errors: [error("An error")]] | [errors: [error("A different error")]]
149+
[data: "A value", errors: [error("An error")]] | [data: "A different value", errors: [error("An error")]]
150+
[data: "A value", localContext: 5] | [data: "A value", localContext: 1]
151+
[data: "A value", errors: [error("An error")], localContext: 5] | [data: "A value", errors: [error("A different error")], localContext: 5]
152+
[data: "A value", extensions: ["key": "value"]] | [data: "A value", extensions: ["key", "different value"]]
153+
[data: "A value", errors: [error("An error")], localContext: 5, extensions: ["key": "value"]] | [data: "A value", errors: [error("An error")], localContext: 5, extensions: ["key": "different value"]]
154+
}
155+
156+
private static DataFetcherResult toDataFetcherResult(Map<String, Object> resultFields) {
157+
def resultBuilder = DataFetcherResult.newResult();
158+
resultFields.forEach { key, value ->
159+
if (value != null) {
160+
switch (key) {
161+
case "data":
162+
resultBuilder.data(value)
163+
break;
164+
case "errors":
165+
resultBuilder.errors(value as List<GraphQLError>);
166+
break;
167+
case "localContext":
168+
resultBuilder.localContext(value);
169+
break;
170+
case "extensions":
171+
resultBuilder.extensions(value as Map<Object, Object>);
172+
break;
173+
}
174+
}
175+
}
176+
return resultBuilder.build();
177+
}
178+
179+
private static GraphQLError error(String message) {
180+
return GraphQLError.newError()
181+
.message(message)
182+
.build();
183+
}
110184
}

src/test/groovy/graphql/execution/instrumentation/DataLoaderCacheCanBeAsyncTest.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class DataLoaderCacheCanBeAsyncTest extends Specification {
8787
def valueCache = new CustomValueCache()
8888
valueCache.store.put("a", [id: "cachedA", name: "cachedAName"])
8989

90-
DataLoaderOptions options = DataLoaderOptions.newOptions().setValueCache(valueCache).setCachingEnabled(true)
90+
DataLoaderOptions options = DataLoaderOptions.newOptions().setValueCache(valueCache).setCachingEnabled(true).build()
9191
DataLoader userDataLoader = DataLoaderFactory.newDataLoader(userBatchLoader, options)
9292

9393
registry = DataLoaderRegistry.newRegistry()

src/test/groovy/graphql/execution/instrumentation/dataloader/BatchCompareDataFetchers.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@ private static List<List<Department>> getDepartmentsForShops(List<Shop> shops) {
100100

101101
public DataLoader<String, List<Department>> departmentsForShopDataLoader = DataLoaderFactory.newDataLoader(departmentsForShopsBatchLoader);
102102

103-
public DataFetcher<CompletableFuture<List<Department>>> departmentsForShopDataLoaderDataFetcher = environment -> {
103+
public DataFetcher<?> departmentsForShopDataLoaderDataFetcher = environment -> {
104104
Shop shop = environment.getSource();
105-
return departmentsForShopDataLoader.load(shop.getId());
105+
return environment.getDataLoader("departments").load(shop.getId());
106106
};
107107

108108
// Products
@@ -136,9 +136,9 @@ private static List<List<Product>> getProductsForDepartments(List<Department> de
136136

137137
public DataLoader<String, List<Product>> productsForDepartmentDataLoader = DataLoaderFactory.newDataLoader(productsForDepartmentsBatchLoader);
138138

139-
public DataFetcher<CompletableFuture<List<Product>>> productsForDepartmentDataLoaderDataFetcher = environment -> {
139+
public DataFetcher<?> productsForDepartmentDataLoaderDataFetcher = environment -> {
140140
Department department = environment.getSource();
141-
return productsForDepartmentDataLoader.load(department.getId());
141+
return environment.getDataLoader("products").load(department.getId());
142142
};
143143

144144
private <T> CompletableFuture<T> maybeAsyncWithSleep(Supplier<CompletableFuture<T>> supplier) {

src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductBackend.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33

44
import com.google.common.collect.ImmutableList;
5+
import org.dataloader.BatchLoader;
56
import org.dataloader.DataLoader;
67
import org.dataloader.DataLoaderFactory;
78

@@ -26,12 +27,13 @@ public DataLoaderCompanyProductBackend(int companyCount, int projectCount) {
2627
mkCompany(projectCount);
2728
}
2829

29-
projectsLoader = DataLoaderFactory.newDataLoader(keys -> getProjectsForCompanies(keys).thenApply(projects -> keys
30+
BatchLoader<UUID, List<Project>> uuidListBatchLoader = keys -> getProjectsForCompanies(keys).thenApply(projects -> keys
3031
.stream()
3132
.map(companyId -> projects.stream()
3233
.filter(project -> project.getCompanyId().equals(companyId))
3334
.collect(Collectors.toList()))
34-
.collect(Collectors.toList())));
35+
.collect(Collectors.toList()));
36+
projectsLoader = DataLoaderFactory.newDataLoader(uuidListBatchLoader);
3537

3638
}
3739

src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductMutationTest.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class DataLoaderCompanyProductMutationTest extends Specification {
4848
newTypeWiring("Company").dataFetcher("projects", {
4949
environment ->
5050
DataLoaderCompanyProductBackend.Company source = environment.getSource()
51-
return backend.getProjectsLoader().load(source.getId())
51+
return environment.getDataLoader("projects-dl").load(source.getId())
5252
}))
5353
.type(
5454
newTypeWiring("Query").dataFetcher("companies", {

src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ class DataLoaderHangingTest extends Specification {
192192
})
193193
}, executor)
194194
}
195-
}, DataLoaderOptions.newOptions().setMaxBatchSize(5))
195+
}, DataLoaderOptions.newOptions().setMaxBatchSize(5).build())
196196

197197
def dataLoaderSongs = DataLoaderFactory.newDataLoader(new BatchLoader<DataFetchingEnvironment, List<Object>>() {
198198
@Override
@@ -209,7 +209,7 @@ class DataLoaderHangingTest extends Specification {
209209
})
210210
}, executor)
211211
}
212-
}, DataLoaderOptions.newOptions().setMaxBatchSize(5))
212+
}, DataLoaderOptions.newOptions().setMaxBatchSize(5).build())
213213

214214
def dataLoaderRegistry = new DataLoaderRegistry()
215215
dataLoaderRegistry.register("artist.albums", dataLoaderAlbums)

src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderNodeTest.groovy

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import graphql.schema.GraphQLObjectType
1111
import graphql.schema.GraphQLSchema
1212
import graphql.schema.StaticDataFetcher
1313
import org.dataloader.DataLoader
14+
import org.dataloader.DataLoaderFactory
1415
import org.dataloader.DataLoaderRegistry
1516
import spock.lang.Specification
1617

@@ -69,39 +70,42 @@ class DataLoaderNodeTest extends Specification {
6970
}
7071

7172
class NodeDataFetcher implements DataFetcher {
72-
DataLoader loader
73+
String name
7374

74-
NodeDataFetcher(DataLoader loader) {
75-
this.loader = loader
75+
NodeDataFetcher(String name) {
76+
this.name = name
7677
}
7778

7879
@Override
7980
Object get(DataFetchingEnvironment environment) throws Exception {
80-
return loader.load(environment.getSource())
81+
return environment.getDataLoader(name).load(environment.getSource())
8182
}
8283
}
8384

8485
def "levels of loading"() {
8586

8687
List<List<Node>> nodeLoads = []
8788

88-
DataLoader<Node, List<Node>> loader = new DataLoader<>({ keys ->
89+
90+
def batchLoadFunction = { keys ->
8991
nodeLoads.add(keys)
9092
List<List<Node>> childNodes = new ArrayList<>()
9193
for (Node key : keys) {
9294
childNodes.add(key.childNodes)
9395
}
9496
System.out.println("BatchLoader called for " + keys + " -> got " + childNodes)
9597
return CompletableFuture.completedFuture(childNodes)
96-
})
97-
98-
DataFetcher<?> nodeDataFetcher = new NodeDataFetcher(loader)
98+
}
99+
DataLoader<Node, List<Node>> loader = DataLoaderFactory.newDataLoader(batchLoadFunction)
99100

100101
def nodeTypeName = "Node"
101102
def childNodesFieldName = "childNodes"
102103
def queryTypeName = "Query"
103104
def rootFieldName = "root"
104105

106+
DataFetcher<?> nodeDataFetcher = new NodeDataFetcher(childNodesFieldName)
107+
DataLoaderRegistry registry = new DataLoaderRegistry().register(childNodesFieldName, loader)
108+
105109
GraphQLObjectType nodeType = GraphQLObjectType
106110
.newObject()
107111
.name(nodeTypeName)
@@ -132,8 +136,6 @@ class DataLoaderNodeTest extends Specification {
132136
.build())
133137
.build()
134138

135-
DataLoaderRegistry registry = new DataLoaderRegistry().register(childNodesFieldName, loader)
136-
137139
ExecutionResult result = GraphQL.newGraphQL(schema)
138140
// .instrumentation(new DataLoaderDispatcherInstrumentation())
139141
.build()

src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderTypeMismatchTest.groovy

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import graphql.schema.idl.SchemaGenerator
99
import graphql.schema.idl.SchemaParser
1010
import org.dataloader.BatchLoader
1111
import org.dataloader.DataLoader
12+
import org.dataloader.DataLoaderFactory
1213
import org.dataloader.DataLoaderRegistry
1314
import spock.lang.Specification
1415

@@ -36,7 +37,7 @@ class DataLoaderTypeMismatchTest extends Specification {
3637

3738
def typeDefinitionRegistry = new SchemaParser().parse(sdl)
3839

39-
def dataLoader = new DataLoader<Object, Object>(new BatchLoader<Object, Object>() {
40+
def dataLoader = DataLoaderFactory.newDataLoader(new BatchLoader<Object, Object>() {
4041
@Override
4142
CompletionStage<List<Object>> load(List<Object> keys) {
4243
return CompletableFuture.completedFuture([
@@ -50,7 +51,7 @@ class DataLoaderTypeMismatchTest extends Specification {
5051
def todosDef = new DataFetcher<CompletableFuture<Object>>() {
5152
@Override
5253
CompletableFuture<Object> get(DataFetchingEnvironment environment) {
53-
return dataLoader.load(environment)
54+
return environment.getDataLoader("getTodos").load(environment)
5455
}
5556
}
5657

src/test/groovy/graphql/execution/instrumentation/dataloader/Issue1178DataLoaderDispatchTest.groovy

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import graphql.schema.StaticDataFetcher
88
import graphql.schema.idl.RuntimeWiring
99
import org.dataloader.BatchLoader
1010
import org.dataloader.DataLoader
11+
import org.dataloader.DataLoaderFactory
1112
import org.dataloader.DataLoaderRegistry
1213
import spock.lang.Specification
1314

@@ -40,15 +41,15 @@ class Issue1178DataLoaderDispatchTest extends Specification {
4041

4142
def executor = Executors.newFixedThreadPool(5)
4243

43-
def dataLoader = new DataLoader<Object, Object>(new BatchLoader<Object, Object>() {
44+
def dataLoader = DataLoaderFactory.newDataLoader(new BatchLoader<Object, Object>() {
4445
@Override
4546
CompletionStage<List<Object>> load(List<Object> keys) {
4647
return CompletableFuture.supplyAsync({
4748
return keys.collect({ [id: 'r' + it] })
4849
}, executor)
4950
}
5051
})
51-
def dataLoader2 = new DataLoader<Object, Object>(new BatchLoader<Object, Object>() {
52+
def dataLoader2 = DataLoaderFactory.newDataLoader(new BatchLoader<Object, Object>() {
5253
@Override
5354
CompletionStage<List<Object>> load(List<Object> keys) {
5455
return CompletableFuture.supplyAsync({
@@ -61,8 +62,8 @@ class Issue1178DataLoaderDispatchTest extends Specification {
6162
dataLoaderRegistry.register("todo.related", dataLoader)
6263
dataLoaderRegistry.register("todo.related2", dataLoader2)
6364

64-
def relatedDf = new MyDataFetcher(dataLoader)
65-
def relatedDf2 = new MyDataFetcher(dataLoader2)
65+
def relatedDf = new MyDataFetcher("todo.related")
66+
def relatedDf2 = new MyDataFetcher("todo.related2")
6667

6768
def wiring = RuntimeWiring.newRuntimeWiring()
6869
.type(newTypeWiring("Query")
@@ -119,16 +120,16 @@ class Issue1178DataLoaderDispatchTest extends Specification {
119120

120121
static class MyDataFetcher implements DataFetcher<CompletableFuture<Object>> {
121122

122-
private final DataLoader dataLoader
123+
private final String name
123124

124-
MyDataFetcher(DataLoader dataLoader) {
125-
this.dataLoader = dataLoader
125+
MyDataFetcher(String name) {
126+
this.name = name
126127
}
127128

128129
@Override
129130
CompletableFuture<Object> get(DataFetchingEnvironment environment) {
130131
def todo = environment.source as Map
131-
return dataLoader.load(todo['id'])
132+
return environment.getDataLoader(name).load(todo['id'])
132133
}
133134
}
134135
}

0 commit comments

Comments
 (0)