-
Notifications
You must be signed in to change notification settings - Fork 371
Description
Excellent framework! This library simply rocks! And so wanted to check how I could use it with the Micronaut framework.
With this regards, need your help to understand what's the best way (graphql-kotlin idomatic way) to set contextual data when using the Micronaut framework. Appreciate that there is fantastic documentation for Spring. But similar love was missing for the Micronaut and Quarkus frameworks :-) which is always very helpful for beginners like me.
Please find a link to my example repo: source code
At a high level, I have followed these steps.
- Extend GraphQLContext that will hold access token when an HTTP request is made on the graphql service
- Extend GraphQLContextFactory that will generate context from incoming HTTP Request
- Extend GraphQLExecutionInputCustomizer that will use the above context factory to generate context from inbound HTTP request
class UserContext(val token: String) : GraphQLContext
@Singleton
class UserContextFactory : GraphQLContextFactory<UserContext, HttpRequest<*>> {
override suspend fun generateContext(request: HttpRequest<*>): UserContext {
val token = request.headers.get("Authorization")?.substringAfter("Bearer")?.trim() ?: ""
println("token as found by context factory: $token")
return UserContext(token = token)
}
}
@Primary// mark it as primary to override the default one
@Singleton
class AuthCustomizer(private val ctxFactory : UserContextFactory) : GraphQLExecutionInputCustomizer {
override fun customize(
executionInput: ExecutionInput?,
httpRequest: HttpRequest<*>?,
httpResponse: MutableHttpResponse<String>?
): Publisher<ExecutionInput>? {
val ctx = runBlocking {
ctxFactory.generateContext(httpRequest!!)
}
return Publishers.just(executionInput?.transform { builder ->
builder.context(ctx)
})
}
}
From graphql-kotlin documentation regarding contextual-data
following are the relevant aspects of the documentation I stumbled upon
The easiest way to specify a context class is to use the GraphQLContext marker interface. This interface does not require any implementations, it is just used to inform the schema generator that this is the class that should be used as the context for every request.
The context is injected into the execution through the FunctionDataFetcher class.
Here I am not sure how to inform schema generator so that is it aware of the defined GraphQLContext. Also, not sure how to use FunctionDataFetcher to inject the context, as it doesn't have easy access to the HTTPRequest.
and from graphql-kotlin documentation regarding graphql-context-factory
GraphQLContextFactory is a generic method for generating a GraphQLContext for each request. Given the generic server request, the interface should create a GraphQLContext class to be used for every new operation. The context must implement the GraphQLContext interface from graphql-kotlin-schema-generator.
For common use cases around authorization, authentication, or tracing you may need to read HTTP headers and cookies. This should be done in the GraphQLContextFactory and relevant data should be added to the context to be accessible during schema execution.
Here I am not sure how to inform schema generator so that is it aware of the defined GraphQLContextFactory (or should it?).
I tried to look into the graphql-kotlin documentation and was not able to identify a way I could code when using Micronaut.
So, I am not really sure, the steps that I have followed is the best way to use this library with the Micronaut framework or there is a better way to do so.
Any pointers will greatly help :-)
Here is the graphql schema configuration part of my code:
@Factory
class GraphQLFactory{
@Bean
@Singleton
@Suppress("unused")
fun graphQL(exceptionHandler: CustomDataFetcherExceptionHandler): GraphQL {
val hooks = object : SchemaGeneratorHooks {
override val wiringFactory: KotlinDirectiveWiringFactory
get() = KotlinDirectiveWiringFactory(manualWiring = mapOf("Authorized" to AuthorizedDirectiveWiring()))
}
val config = SchemaGeneratorConfig(
supportedPackages = listOf("hello.models"),
dataFetcherFactoryProvider = CustomDataFetcherFactoryProvider(objectMapper = ObjectMapper()),
hooks = hooks
)
val queries = listOf(
TopLevelObject(AccountsQuery())
)
return GraphQL.newGraphQL(toSchema(config, queries, emptyList()))
.queryExecutionStrategy(AsyncExecutionStrategy(exceptionHandler))
.mutationExecutionStrategy(AsyncExecutionStrategy(exceptionHandler))
.build()
}
}