graphql-java-kickstart / graphql-java-tools

A schema-first tool for graphql-java inspired by graphql-tools for JS
https://www.graphql-java-kickstart.com/tools/
MIT License
808 stars 174 forks source link

Co-routine support in directives. #305

Open alacoste opened 5 years ago

alacoste commented 5 years ago

First of all thanks for the great library, it has been working amazing for us so far!

We were happily surprised when graphql-java-kickstart worked immediately with suspend resolvers. We would now like to use GraphQL directives and are hoping there is similar support for it, however I couldn't find the way to make it work. I also didn't find the equivalent of https://github.com/graphql-java-kickstart/graphql-java-tools/issues/104 for suspending directives.

Is our use case already supported?

Reasons we would like to make the directive logic suspending: we want to use directives for authorization and would like to make on-the-fly database calls when we need to validate permissions in the directive.

alacoste commented 4 years ago

For anyone looking at this, we are currently using the following workaround:

// Returns a modified DataFetcher that runs code before the normal resolution. This is typically useful to verify
// sufficient permissions before performing an operation.
//
// This is the pendant of DataFetcherFactories.wrapDataFetcher that allows to run code after the normal resolution to
// modify the returned object.
fun DataFetcher<*>.withPreprocessing(
    block: suspend (DataFetchingEnvironment) -> Unit
): (DataFetchingEnvironment) -> CompletableFuture<Any?> = { env: DataFetchingEnvironment ->
    GlobalScope.async {
        block(env)
        this@withPreprocessing.get(env).awaitIfCompletableFuture()
    }.asCompletableFuture()
}

// If the receiver object is a CompletableFuture, awaits it. Otherwise passes the object through.
suspend fun Any?.awaitIfCompletableFuture(): Any? = if (this is CompletableFuture<*>) {
    this.await()
} else {
    this
}

// Example directive object.
object MyDirective : SchemaDirectiveWiring {
    override fun onField(directiveEnv: SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition>): GraphQLFieldDefinition {
        val originalDataFetcher: DataFetcher<*> = directiveEnv.fieldDataFetcher

        directiveEnv.setFieldDataFetcher(originalDataFetcher.withPreprocessing { env: DataFetchingEnvironment -> /* your custom suspending logic here */ })
        return directiveEnv.element
    }
}