Hi <@UGUMP8USE> does graphql kotlin spring server ...
# graphql-kotlin
f
Hi @Dariusz Kuc does graphql kotlin spring server support injection of graphql context into any spring service ? Is it possible to put the value into a webflux context in webflux webfilter and retrieve it in spring service? Eventually webflux context got cleared before graphql mutation and query. With respect.
d
👋
graphql-kotlin
supports automatic context propagation since v6 (see https://github.com/ExpediaGroup/graphql-kotlin/releases/tag/6.0.0) so if you add something to the Reactor context in the WebFilter, it will be accessible from the GraphQL context. GraphQL context is not a Spring bean so it cannot be autowired but you can access it through
DataFetchingEnvironment
- see https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/execution/contextual-data#graphql-context-map
f
Thank you for your response. But am I able to set something in
Copy code
org.springframework.web.server.WebFilter
and reuse it in any place like
Mono.deferContextual(...)
? Use case is that I have to read header parameters and put it inside webclient’s header. Graphql context looks overloaded. I can put it only on Mutation/Query level and then send in each function as a parameter which is not the best way.
d
As long as you are running coroutines then yes it should have access to the reactor context (due to reactor <-> coroutine interop)
f
But I have an empty context in mutation unfortunately
Draft code for filter
Copy code
@Component
class UserWebFilter : WebFilter {

    override fun filter(serverWebExchange: ServerWebExchange, webFilterChain: WebFilterChain): Mono<Void> =
        webFilterChain
            .filter(serverWebExchange)
            .contextWrite {
                context(it, serverWebExchange)
            }.then()

    private fun context(
        it: Context,
        serverWebExchange: ServerWebExchange
    ): Context {
        val context = putHeaderToContext(
            putHeaderToContext(it, serverWebExchange, X_USER_ID, UUID::fromString),
            serverWebExchange,
            AUTHORIZATION
        ) { a -> a!! }
        return context
    }

    private fun getHeader(serverWebExchange: ServerWebExchange, headerName: String) =
        serverWebExchange
            .request
            .headers
            .getFirst(headerName)

    private fun putHeaderToContext(
        context: Context,
        serverWebExchange: ServerWebExchange,
        headerName: String,
        transformFunction: Function<String?, Any>
    ): Context {
        val headerValue: String? = getHeader(serverWebExchange, headerName)
        return if (ObjectUtils.isNotEmpty(headerValue))
            context.put(
                headerName,
                transformFunction.apply(headerValue)
            ) else context
//                throw ForbiddenRequestException(MISSED_REQUIRED_HEADER_PARAMETERS)
    }
}
d
Make sure you are using v6+
f
Copy code
<graphql-kotlin-spring-server.version>6.3.0</graphql-kotlin-spring-server.version>
and for the query
d
See some discussions/issues on the repo that talk about auth
There is some example code there
f
Copy code
@Component
class BoardQuery(
    private val boardService: BoardService
) : Query {

    fun getBoardFull(
        id: UUID
    ) = Mono.deferContextual { c -> boardService.getBoard(id, c)}
context
under
deferCotextual
is empty
I’ve found examples with the following
Copy code
@Component
class ContextualQuery : Query {

    @GraphQLDescription("query that uses GraphQLContext context")
    fun contextualQuery(
        @GraphQLDescription("some value that will be returned to the user")
        value: Int,
        env: DataFetchingEnvironment
    ): ContextualResponse = ContextualResponse(value, env.graphQlContext.getOrDefault("myCustomValue", "defaultValue"))
}
The key difference is that I have to use
Webflux Context
instead of
DataFetchingEnvironment
🤔
@Dariusz Kuc please help.
d
as I wrote above ->
Copy code
As long as you are running coroutines then yes it should have access to the reactor context (due to reactor <-> coroutine interop)
Default data fetcher logic runs the coroutines in the original scope (https://github.com/ExpediaGroup/graphql-kotlin/blob/master/generator/graphql-kotli[…]expediagroup/graphql/generator/execution/FunctionDataFetcher.kt). Your method (
fun getBoardFull
) is NOT a coroutine hence it won't get the context set. You will either have to use custom data fetcher that wraps up the Mono execution OR switch to coroutines.
injection of
DataFetchingEnvironment
is automatic for both coroutine and regular functions
f
Now it's clear thanks. I'll try to apply your comments. Appreciate your support.
Let me post here how I’ve resolved it
Copy code
import com.expediagroup.graphql.server.spring.execution.SpringDataFetcher
...

/**
 * Custom function data fetcher that adds support for Reactor Mono.
 */
class CustomFunctionDataFetcher(
    target: Any?,
    fn: KFunction<*>,
    appContext: ApplicationContext
) : SpringDataFetcher(target, fn, appContext) {

    override fun get(environment: DataFetchingEnvironment): Any? {
        val context = environment.getContext<GraphQLRequestContext>()
        return when (val result = super.get(environment)) {
            is Mono<*> -> result.contextWrite { c ->
                c.put(X_USER_ID, context.xUserId)
                    .put(AUTHORIZATION, context.authorization)
            }.toFuture()

            else -> result
        }
    }
}
Thanks to @Dariusz Kuc for support. 🙏