graphql-kotlin: Reactor - Flux and Mono can't get them to work at all

So I’ve been hammering away at this for a day and I can’t seem to get even basic queries to work with mono or flux.

Below is an example of a query to a user profile from the identity store. If I convert this into a future it works out of the box, but the moment I use a mono i get all my fields as null

{"data":{"currentUserProfile":{"firstname":null,"lastname":null,"username":null,"emailAddress":null}}}

And the query code:

@PreAuthorize("isAuthenticated()")
    fun currentUserProfile(context: GraphQLSecurityContext): Mono<UserProfile> {
        return context.securityContext.flatMap { sc ->
            val details = sc.authentication.details as UserCredential
            userIdentityService.findById(details.id).map { UserProfile.fromUserIdentity(it) }
        }

I don’t seem to have a problem generating schema with mono. I have registered a monad hook, but it seems to do nothing.

Is there something I’m doing wrong? I know it does call the query method, but I can’t seem to figure out I get a null response.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 1
  • Comments: 32 (5 by maintainers)

Most upvoted comments

Anyway, thanks for the help.

Here is the code for anyone that wants it. Obviously a todo will be to add expression support:

class AuthorisedDataFetcher(private val originDataFetcher: DataFetcher<Any?>, val roles: Array<out String>) : DataFetcher<Any?> {

    val logger = LoggerFactory.getLogger(AuthorisedDataFetcher::class.java)

    @Throws(AccessDeniedException::class)
    override fun get(environment: DataFetchingEnvironment): Any? {
        val securityContext = environment.getContext<GraphQLSecurityContext>().securityContext
        val authentication = securityContext.authentication
        if (!authentication.isAuthenticated || (roles.isNotEmpty() && authentication.authorities.filter {
                    roles.contains(it.authority) }.none())) {
            throw AccessDeniedException("Access denied") // TODO Make sure this gets logged for audit purposes
        }
        return originDataFetcher.get(environment)
    }
}
@Component
class ReactiveDataFactoryProvider(val dataFetcherFactory: ReactiveDataFetcherFactory, val objectMapper: ObjectMapper) :
        SimpleKotlinDataFetcherFactoryProvider(objectMapper) {

    override fun functionDataFetcherFactory(target: Any?, kFunction: KFunction<*>) = DataFetcherFactory {
        val authorised = kFunction.findAnnotation<Authorised>()
        val reactiveDataFetcher = ReactiveFunctionDataFetcher(
                target = target,
                fn = kFunction,
                objectMapper = objectMapper)
        when {
            authorised != null -> AuthorisedDataFetcher(reactiveDataFetcher, authorised.roles)
            else -> reactiveDataFetcher
        }
    }

    override fun propertyDataFetcherFactory(kClass: KClass<*>, kProperty: KProperty<*>): DataFetcherFactory<Any?> =
            if (kProperty.isLateinit) {
                dataFetcherFactory
            } else {
                super.propertyDataFetcherFactory(kClass, kProperty)
            }
}
@Component
class ReactiveDataFetcherFactory: DataFetcherFactory<Any?>, BeanFactoryAware {
    private lateinit var beanFactory: BeanFactory

    override fun setBeanFactory(beanFactory: BeanFactory) {
        this.beanFactory = beanFactory
    }

    @Suppress("UNCHECKED_CAST")
    override fun get(environment: DataFetcherFactoryEnvironment?): DataFetcher<Any?> {

        // Strip out possible `Input` and `!` suffixes added to by the SchemaGenerator
        val targetedTypeName = environment?.fieldDefinition?.type?.deepName?.removeSuffix("!")?.removeSuffix("Input")
        return beanFactory.getBean("${targetedTypeName}DataFetcher") as DataFetcher<Any?>
    }
}
class ReactiveFunctionDataFetcher(target: Any?, fn: KFunction<*>, objectMapper: ObjectMapper): FunctionDataFetcher(target, fn, objectMapper) {

    override fun get(environment: DataFetchingEnvironment): Any? = when (val result = super.get(environment)) {
        is Mono<*> -> result.toFuture()
        else -> result
    }
}

Edit: Forgot the annotation

@GraphQLDirective(
        name = "authorised",
        description = "Used to check authorisation"
)
annotation class Authorised(vararg val roles: String)

Example:

@Authorised()
    suspend fun currentUserProfile(context: GraphQLSecurityContext): UserProfile? {
        logger.debug("Query executed")
        return mono { context.securityContext }.flatMap { sc ->
            val details = sc.authentication.details as UserCredential
            userIdentityService.findById(details.id).map { UserProfile.fromUserIdentity(it) }
        }.awaitFirst()
    }