graphql-java-tools icon indicating copy to clipboard operation
graphql-java-tools copied to clipboard

The detail message from Checked Exceptions thrown by Resolvers is ignored in ExecutionResult.getErrors()

Open zleonov opened this issue 2 years ago • 2 comments

Description

graphql-java-tools maps fields on GraphQL objects to methods and properties on "Resolvers" and "Data Classes" thus eliminating the boilerplate code required to setup graphql-java manually. Resolver methods are invoked via reflection and are free to throw both checked and unchecked exceptions in their method signatures.

When an exception is thrown it eventually ends up as a GraphQLError object in the ExecutionResult.getErrors() list. For runtime exceptions the resulting GraphQLError object inherits their error messages. But for checked exceptions or Throwables (which the Resolver methods are technically free to throw as well) the detail message is lost and a null is returned.

This problem does not exist in graphql-java.

Expected behavior

The detail message from Checked Exceptions thrown by Resolvers should be propogated to GraphQLError.getMessage() returned from ExecutionResult.getErrors().

Actual behavior

GraphQLError objects corresponding to Checked Exceptions thrown by Resolvers return a null in their getMessage().

Steps to reproduce the bug

  1. Create a simple hello-world demo using a GraphQLQueryResolver (and write the schema accordingly)
  2. Once the demo works, change the hello() method to throw a checked exception with a message.
  3. Examine the errors in ExecutionResult.

I have a gist that clearly demonstrates the bug: https://gist.github.com/zleonov/32f45aefdce49d4666877f4d276d5c2a

zleonov avatar Oct 11 '22 03:10 zleonov

I took a cursory look at the source code and I think this is where the bug occurs:

src\main\kotlin\graphql\kickstart\tools\resolver\MethodFieldResolver.kt(267):

@Suppress("NOTHING_TO_INLINE")
private inline fun invoke(method: Method, instance: Any, args: Array<Any?>): Any? {
    try {
        return method.invoke(instance, *args)
    } catch (invocationException: java.lang.reflect.InvocationTargetException) {
        val e = invocationException.cause
        if (e is RuntimeException) {
            throw e
        }
        if (e is Error) {
            throw e
        }

        throw java.lang.reflect.UndeclaredThrowableException(e)
    }
}

UndeclaredThrowableException does not inherit the detailed message from the underlying exception and as far as I can tell that exception is not unwrapped downstream. UndeclaredThrowableException.getMessage() returns a null.

zleonov avatar Oct 11 '22 03:10 zleonov

I just realized this may be the same issue as #477

zleonov avatar Oct 11 '22 04:10 zleonov

Fixed in https://github.com/graphql-java-kickstart/graphql-java-tools/pull/757

oryan-block avatar Aug 02 '23 15:08 oryan-block