From 723b840191974b1acc45622ed3f0c3cc8aac954d Mon Sep 17 00:00:00 2001 From: Alexandre Carlton Date: Wed, 14 May 2025 20:23:02 +1000 Subject: [PATCH] Implement toString/hashCode/equals for DataFetcherResult This facilitates usage in tests for quick checking of equality (and useful error messages in assertion messages). References #3963. --- .../graphql/execution/DataFetcherResult.java | 41 +++++++++- .../execution/DataFetcherResultTest.groovy | 74 +++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/execution/DataFetcherResult.java b/src/main/java/graphql/execution/DataFetcherResult.java index 0ad53dd38b..9aecf3919b 100644 --- a/src/main/java/graphql/execution/DataFetcherResult.java +++ b/src/main/java/graphql/execution/DataFetcherResult.java @@ -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; @@ -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()} - * + *

* 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. + *

+ * 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: + *

* * @param The type of the data fetched */ @@ -125,6 +135,35 @@ public DataFetcherResult 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 * diff --git a/src/test/groovy/graphql/execution/DataFetcherResultTest.groovy b/src/test/groovy/graphql/execution/DataFetcherResultTest.groovy index 35fbfe2f1d..07318afa75 100644 --- a/src/test/groovy/graphql/execution/DataFetcherResultTest.groovy +++ b/src/test/groovy/graphql/execution/DataFetcherResultTest.groovy @@ -1,5 +1,6 @@ package graphql.execution +import graphql.GraphQLError import graphql.InvalidSyntaxError import graphql.validation.ValidationError import graphql.validation.ValidationErrorType @@ -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 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); + break; + case "localContext": + resultBuilder.localContext(value); + break; + case "extensions": + resultBuilder.extensions(value as Map); + break; + } + } + } + return resultBuilder.build(); + } + + private static GraphQLError error(String message) { + return GraphQLError.newError() + .message(message) + .build(); + } }