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
41 changes: 40 additions & 1 deletion src/main/java/graphql/execution/DataFetcherResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

Expand All @@ -25,10 +26,19 @@
* This also allows you to pass down new local context objects between parent and child fields. If you return a
* {@link #getLocalContext()} value then it will be passed down into any child fields via
* {@link graphql.schema.DataFetchingEnvironment#getLocalContext()}
*
* <p>
* You can also have {@link DataFetcher}s contribute to the {@link ExecutionResult#getExtensions()} by returning
* extensions maps that will be merged together via the {@link graphql.extensions.ExtensionsBuilder} and its {@link graphql.extensions.ExtensionsMerger}
* in place.
* <p>
* This provides {@link #hashCode()} and {@link #equals(Object)} methods that afford comparison with other {@link DataFetcherResult} object.s
* 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:
* <ul>
* <li>The data returned in {@link #getData()}.
* <li>The individual errors returned in {@link #getErrors()}.
* <li>The context returned in {@link #getLocalContext()}.
* <li>The keys/values in the {@link #getExtensions()} {@link Map}.
* </ul>
*
* @param <T> The type of the data fetched
*/
Expand Down Expand Up @@ -125,6 +135,35 @@ public <R> DataFetcherResult<R> map(Function<@Nullable T, @Nullable R> transform
.build();
}


@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) {
return false;
}

DataFetcherResult<?> that = (DataFetcherResult<?>) o;
return Objects.equals(data, that.data)
&& errors.equals(that.errors)
&& Objects.equals(localContext, that.localContext)
&& Objects.equals(extensions, that.extensions);
}

@Override
public int hashCode() {
return Objects.hash(data, errors, localContext, extensions);
}

@Override
public String toString() {
return "DataFetcherResult{" +
"data=" + data +
", errors=" + errors +
", localContext=" + localContext +
", extensions=" + extensions +
'}';
}

/**
* Creates a new data fetcher result builder
*
Expand Down
74 changes: 74 additions & 0 deletions src/test/groovy/graphql/execution/DataFetcherResultTest.groovy
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package graphql.execution

import graphql.GraphQLError
import graphql.InvalidSyntaxError
import graphql.validation.ValidationError
import graphql.validation.ValidationErrorType
Expand Down Expand Up @@ -107,4 +108,77 @@ class DataFetcherResultTest extends Specification {
result.getExtensions() == [a : "b"]
result.getErrors() == [error1, error2]
}

def "implements equals/hashCode for matching results"() {
when:
def firstResult = toDataFetcherResult(first)
def secondResult = toDataFetcherResult(second)

then:
firstResult == secondResult
firstResult.hashCode() == secondResult.hashCode()

where:
first | second
[data: "A string"] | [data: "A string"]
[data: 5] | [data: 5]
[data: ["a", "b"]] | [data: ["a", "b"]]
[errors: [error("An error")]] | [errors: [error("An error")]]
[data: "A value", errors: [error("An error")]] | [data: "A value", errors: [error("An error")]]
[data: "A value", localContext: 5] | [data: "A value", localContext: 5]
[data: "A value", errors: [error("An error")], localContext: 5] | [data: "A value", errors: [error("An error")], localContext: 5]
[data: "A value", extensions: ["key": "value"]] | [data: "A value", extensions: ["key": "value"]]
[data: "A value", errors: [error("An error")], localContext: 5, extensions: ["key": "value"]] | [data: "A value", errors: [error("An error")], localContext: 5, extensions: ["key": "value"]]
}

def "implements equals/hashCode for different results"() {
when:
def firstResult = toDataFetcherResult(first)
def secondResult = toDataFetcherResult(second)

then:
firstResult != secondResult
firstResult.hashCode() != secondResult.hashCode()

where:
first | second
[data: "A string"] | [data: "A different string"]
[data: 5] | [data: "not 5"]
[data: ["a", "b"]] | [data: ["a", "c"]]
[errors: [error("An error")]] | [errors: [error("A different error")]]
[data: "A value", errors: [error("An error")]] | [data: "A different value", errors: [error("An error")]]
[data: "A value", localContext: 5] | [data: "A value", localContext: 1]
[data: "A value", errors: [error("An error")], localContext: 5] | [data: "A value", errors: [error("A different error")], localContext: 5]
[data: "A value", extensions: ["key": "value"]] | [data: "A value", extensions: ["key", "different value"]]
[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"]]
}

private static DataFetcherResult toDataFetcherResult(Map<String, Object> resultFields) {
def resultBuilder = DataFetcherResult.newResult();
resultFields.forEach { key, value ->
if (value != null) {
switch (key) {
case "data":
resultBuilder.data(value)
break;
case "errors":
resultBuilder.errors(value as List<GraphQLError>);
break;
case "localContext":
resultBuilder.localContext(value);
break;
case "extensions":
resultBuilder.extensions(value as Map<Object, Object>);
break;
}
}
}
return resultBuilder.build();
}

private static GraphQLError error(String message) {
return GraphQLError.newError()
.message(message)
.build();
}
}
Loading