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
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ public interface DataFetcherExceptionHandler {
* version
*/
@Deprecated
DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters);
default DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
return SimpleDataFetcherExceptionHandler.defaultImpl.onException(handlerParameters);

Choose a reason for hiding this comment

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

Couldn't this be just IllegalStateException? Either a class implements the new method, in which case this is not used, or it overrides this one. Either way this implementation should never be called.

Copy link
Member Author

Choose a reason for hiding this comment

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

The problem is more when do we decide to break older implementers. if the new CF based handleException does not call the older onException then every one out there that previous implemented onException will break. Because our code will call their method.

Do really it's a case of deciding WHEN we break backwards compat in behavior. That is all those old onException methods will stop working even if its deprecrated.

So ... should that be now? This new CF based method was introduced in 17.x - 18.x is next. Is one version enough? I kinda think it should be two versions

Choose a reason for hiding this comment

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

Just to clarify, the new handleException should continue to call onException as it does now. I'm not suggesting otherwise. I'm not sure how long the deprecation period should continue, but I see no reason not to give it ample time. When that's over, drop onException and remove the the default implementation from handleException.

}
Copy link
Member Author

Choose a reason for hiding this comment

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

Should we undefault the other method in this same PR (and hence 18.0 version)?

Choose a reason for hiding this comment

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

I think it's better to keep default implementation on the new method or otherwise existing implementations would not work until they implement it.


/**
* When an exception occurs during a call to a {@link DataFetcher} then this handler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import graphql.util.LogKit;
import org.slf4j.Logger;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;

/**
Expand All @@ -17,6 +18,8 @@ public class SimpleDataFetcherExceptionHandler implements DataFetcherExceptionHa

private static final Logger logNotSafe = LogKit.getNotPrivacySafeLogger(SimpleDataFetcherExceptionHandler.class);

static final SimpleDataFetcherExceptionHandler defaultImpl = new SimpleDataFetcherExceptionHandler();

@Override
public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
Throwable exception = unwrap(handlerParameters.getException());
Expand All @@ -29,9 +32,15 @@ public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandler
return DataFetcherExceptionHandlerResult.newResult().error(error).build();
}

@Override
public CompletableFuture<DataFetcherExceptionHandlerResult> handleException(DataFetcherExceptionHandlerParameters handlerParameters) {
return CompletableFuture.completedFuture(onException(handlerParameters));
}

/**
* Called to log the exception - a subclass could choose to something different in logging terms
* @param error the graphql error
*
* @param error the graphql error
* @param exception the exception that happened
*/
protected void logException(ExceptionWhileDataFetching error, Throwable exception) {
Expand Down
15 changes: 12 additions & 3 deletions src/test/groovy/graphql/GraphQLTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import graphql.collect.ImmutableKit
import graphql.execution.AsyncExecutionStrategy
import graphql.execution.AsyncSerialExecutionStrategy
import graphql.execution.DataFetcherExceptionHandler
import graphql.execution.DataFetcherExceptionHandlerParameters
import graphql.execution.DataFetcherExceptionHandlerResult
import graphql.execution.DataFetcherResult
import graphql.execution.ExecutionContext
import graphql.execution.ExecutionId
Expand Down Expand Up @@ -1156,16 +1158,23 @@ many lines''']
er.data["f"] == "hi"
}


def "can set default fetcher exception handler"() {


def sdl = 'type Query { f : String } '

DataFetcher df = { env ->
throw new RuntimeException("BANG!")
}
def capturedMsg = null
def exceptionHandler = { params ->
capturedMsg = params.exception.getMessage()
} as DataFetcherExceptionHandler
def exceptionHandler = new DataFetcherExceptionHandler() {
@Override
CompletableFuture<DataFetcherExceptionHandlerResult> handleException(DataFetcherExceptionHandlerParameters params) {
capturedMsg = params.exception.getMessage()
return CompletableFuture.completedFuture(DataFetcherExceptionHandlerResult.newResult().build())
}
}
def schema = TestUtil.schema(sdl, [Query: [f: df]])
def graphQL = GraphQL.newGraphQL(schema).defaultDataFetcherExceptionHandler(exceptionHandler).build()
when:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ class DataFetcherExceptionHandlerTest extends Specification {
def "integration test to prove custom error handle can be made"() {
DataFetcherExceptionHandler handler = new DataFetcherExceptionHandler() {
@Override
DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
CompletableFuture<DataFetcherExceptionHandlerResult> handleException(DataFetcherExceptionHandlerParameters handlerParameters) {
def msg = "The thing went " + handlerParameters.getException().getMessage()
return DataFetcherExceptionHandlerResult.newResult().error(new CustomError(msg, handlerParameters.getSourceLocation())).build()
return CompletableFuture.completedFuture(
DataFetcherExceptionHandlerResult.newResult().error(new CustomError(msg, handlerParameters.getSourceLocation())).build()
)
}
}

Expand Down Expand Up @@ -99,7 +101,7 @@ class DataFetcherExceptionHandlerTest extends Specification {
def "if an exception handler itself throws an exception than that is handled"() {
DataFetcherExceptionHandler handler = new DataFetcherExceptionHandler() {
@Override
DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
CompletableFuture<DataFetcherExceptionHandlerResult> handleException(DataFetcherExceptionHandlerParameters handlerParameters) {
throw new RuntimeException("The handler itself went BANG!")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -575,16 +575,18 @@ class ExecutionStrategyTest extends Specification {

boolean handlerCalled = false
ExecutionStrategy overridingStrategy = new AsyncExecutionStrategy(new SimpleDataFetcherExceptionHandler() {


@Override
DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
CompletableFuture<DataFetcherExceptionHandlerResult> handleException(DataFetcherExceptionHandlerParameters handlerParameters) {
handlerCalled = true
assert handlerParameters.exception == expectedException
assert handlerParameters.fieldDefinition == fieldDefinition
assert handlerParameters.field.name == 'someField'
assert handlerParameters.path == expectedPath

// by calling down we are testing the base class as well
super.onException(handlerParameters)
super.handleException(handlerParameters)
}
}) {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@ class SimpleDataFetcherExceptionHandlerTest extends Specification {
! result.errors[0].getMessage().contains("BANG")
}

static class MyHandler implements DataFetcherExceptionHandler {}

def "a class can work without implementing anything"() {
when:
DataFetcherExceptionHandler handler = new MyHandler()
def handlerParameters = mkParams(new RuntimeException("RTE"))
def result = handler.onException(handlerParameters)

then:
result.errors[0] instanceof ExceptionWhileDataFetching
result.errors[0].getMessage().contains("RTE")

when:
def resultCF = handler.handleException(handlerParameters)

then:
resultCF.join().errors[0] instanceof ExceptionWhileDataFetching
resultCF.join().errors[0].getMessage().contains("RTE")
}

private static DataFetcherExceptionHandlerParameters mkParams(Exception exception) {
def mergedField = newMergedField(newField("f").build()).build()
def esi = newExecutionStepInfo()
Expand Down