diff --git a/spring-graphql-rsocket-kotlin-co/src/main/kotlin/com/example/demo/ValidationConfig.kt b/spring-graphql-rsocket-kotlin-co/src/main/kotlin/com/example/demo/ValidationConfig.kt deleted file mode 100644 index 4545b04b8..000000000 --- a/spring-graphql-rsocket-kotlin-co/src/main/kotlin/com/example/demo/ValidationConfig.kt +++ /dev/null @@ -1,76 +0,0 @@ -package com.example.demo - -import org.hibernate.validator.internal.engine.DefaultClockProvider -import org.springframework.beans.factory.config.BeanDefinition -import org.springframework.boot.validation.MessageInterpolatorFactory -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.context.annotation.Primary -import org.springframework.context.annotation.Role -import org.springframework.core.KotlinReflectionParameterNameDiscoverer -import org.springframework.core.StandardReflectionParameterNameDiscoverer -import org.springframework.core.ParameterNameDiscoverer -import org.springframework.core.PrioritizedParameterNameDiscoverer -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean -import java.lang.reflect.Constructor -import java.lang.reflect.Method -import jakarta.validation.ClockProvider -import jakarta.validation.ParameterNameProvider -import kotlin.reflect.jvm.kotlinFunction - -// workaround for validation on Kotlin Coroutines controller. -// see: https://github.com/spring-projects/spring-framework/issues/23499#issuecomment-900369875 -// related issues see: https://github.com/spring-projects/spring-framework/issues/23499 -// and https://hibernate.atlassian.net/browse/HV-1638 -@Configuration -class ValidationConfig { - @Primary - @Bean - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - fun defaultValidator(): LocalValidatorFactoryBean { - val factoryBean = KotlinCoroutinesLocalValidatorFactoryBean() - factoryBean.messageInterpolator = MessageInterpolatorFactory().getObject() - return factoryBean - } -} - -class KotlinCoroutinesLocalValidatorFactoryBean : LocalValidatorFactoryBean() { - override fun getClockProvider(): ClockProvider = DefaultClockProvider.INSTANCE - - override fun postProcessConfiguration(configuration: jakarta.validation.Configuration<*>) { - super.postProcessConfiguration(configuration) - - val discoverer = PrioritizedParameterNameDiscoverer() - discoverer.addDiscoverer(SuspendAwareKotlinParameterNameDiscoverer()) - discoverer.addDiscoverer(StandardReflectionParameterNameDiscoverer()) - - val defaultProvider = configuration.defaultParameterNameProvider - configuration.parameterNameProvider(object : ParameterNameProvider { - override fun getParameterNames(constructor: Constructor<*>): List { - val paramNames: Array? = discoverer.getParameterNames(constructor) - return paramNames?.toList() ?: defaultProvider.getParameterNames(constructor) - } - - override fun getParameterNames(method: Method): List { - val paramNames: Array? = discoverer.getParameterNames(method) - return paramNames?.toList() ?: defaultProvider.getParameterNames(method) - } - }) - } -} - -class SuspendAwareKotlinParameterNameDiscoverer : ParameterNameDiscoverer { - - private val defaultProvider = KotlinReflectionParameterNameDiscoverer() - - override fun getParameterNames(constructor: Constructor<*>): Array? = - defaultProvider.getParameterNames(constructor) - - override fun getParameterNames(method: Method): Array? { - val defaultNames = defaultProvider.getParameterNames(method) ?: return null - val function = method.kotlinFunction - return if (function != null && function.isSuspend) { - defaultNames + "" - } else defaultNames - } -} \ No newline at end of file diff --git a/spring-graphql-rsocket-kotlin-co/src/main/kotlin/com/example/demo/gql/scalars/LocalDateTimeScalar.kt b/spring-graphql-rsocket-kotlin-co/src/main/kotlin/com/example/demo/gql/scalars/LocalDateTimeScalar.kt index b325d02ea..68cbc793e 100644 --- a/spring-graphql-rsocket-kotlin-co/src/main/kotlin/com/example/demo/gql/scalars/LocalDateTimeScalar.kt +++ b/spring-graphql-rsocket-kotlin-co/src/main/kotlin/com/example/demo/gql/scalars/LocalDateTimeScalar.kt @@ -1,33 +1,43 @@ package com.example.demo.gql.scalars +import graphql.GraphQLContext +import graphql.execution.CoercedVariables import graphql.language.StringValue +import graphql.language.Value import graphql.schema.Coercing import graphql.schema.CoercingParseLiteralException -import graphql.schema.CoercingParseValueException import graphql.schema.CoercingSerializeException import java.time.LocalDateTime import java.time.format.DateTimeFormatter +import java.util.* class LocalDateTimeScalar : Coercing { @Throws(CoercingSerializeException::class) - override fun serialize(dataFetcherResult: Any): String? { + override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): String? { return when (dataFetcherResult) { is LocalDateTime -> dataFetcherResult.format(DateTimeFormatter.ISO_DATE_TIME) else -> throw CoercingSerializeException("Not a valid DateTime") } } - @Throws(CoercingParseValueException::class) - override fun parseValue(input: Any): LocalDateTime { + override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): LocalDateTime { return LocalDateTime.parse(input.toString(), DateTimeFormatter.ISO_DATE_TIME) } - @Throws(CoercingParseLiteralException::class) - override fun parseLiteral(input: Any): LocalDateTime { + override fun parseLiteral( + input: Value<*>, + variables: CoercedVariables, + graphQLContext: GraphQLContext, + locale: Locale + ): LocalDateTime { when (input) { is StringValue -> return LocalDateTime.parse(input.value, DateTimeFormatter.ISO_DATE_TIME) else -> throw CoercingParseLiteralException("Value is not a valid ISO date time") } } + override fun valueToLiteral(input: Any, graphQLContext: GraphQLContext, locale: Locale): Value<*> { + return StringValue(input.toString()) + } + } \ No newline at end of file diff --git a/spring-graphql-rsocket-kotlin-co/src/test/kotlin/com/example/demo/IntegrationTests.kt b/spring-graphql-rsocket-kotlin-co/src/test/kotlin/com/example/demo/IntegrationTests.kt index 14493b8e6..2ec270f0c 100644 --- a/spring-graphql-rsocket-kotlin-co/src/test/kotlin/com/example/demo/IntegrationTests.kt +++ b/spring-graphql-rsocket-kotlin-co/src/test/kotlin/com/example/demo/IntegrationTests.kt @@ -3,7 +3,7 @@ package com.example.demo import com.example.demo.gql.types.Comment import com.example.demo.gql.types.Post import com.fasterxml.jackson.databind.ObjectMapper -import io.kotest.assertions.timing.continually +import io.kotest.assertions.nondeterministic.continually import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -25,7 +25,6 @@ import java.util.* import java.util.concurrent.CopyOnWriteArrayList import kotlin.time.Duration.Companion.seconds -@OptIn(ExperimentalCoroutinesApi::class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) internal class IntegrationTests { companion object { diff --git a/spring-graphql-rsocket-kotlin-co/src/test/kotlin/com/example/demo/QueryTests.kt b/spring-graphql-rsocket-kotlin-co/src/test/kotlin/com/example/demo/QueryTests.kt index 2ebe90ab6..bc4bf22ef 100644 --- a/spring-graphql-rsocket-kotlin-co/src/test/kotlin/com/example/demo/QueryTests.kt +++ b/spring-graphql-rsocket-kotlin-co/src/test/kotlin/com/example/demo/QueryTests.kt @@ -3,27 +3,22 @@ package com.example.demo import com.example.demo.gql.datafetchers.PostController import com.example.demo.gql.types.Post import com.ninjasquad.springmockk.MockkBean -import io.kotest.matchers.collections.shouldContainAll import io.kotest.matchers.ints.shouldBeGreaterThan import io.kotest.matchers.shouldBe import io.mockk.coEvery import io.mockk.coVerify -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Test import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest -import org.springframework.context.annotation.Import import org.springframework.graphql.ResponseError import org.springframework.graphql.test.tester.GraphQlTester import java.time.LocalDateTime import java.util.* -@OptIn(ExperimentalCoroutinesApi::class) @GraphQlTest(controllers = [PostController::class]) -@Import(ValidationConfig::class) internal class QueryTests { companion object { private val log = LoggerFactory.getLogger(QueryTests::class.java) @@ -61,9 +56,9 @@ internal class QueryTests { // graphQlTester.document(query).execute() // .errors().satisfy { it.forEach { error -> log.debug("error message: ${error.message}") } } graphQlTester.document(query) - .execute() - .path("data.allPosts[*].title") - .entityList(String::class.java).hasSize(2).contains("POST 1", "POST 2") + .execute() + .path("data.allPosts[*].title") + .entityList(String::class.java).hasSize(2).contains("POST 1", "POST 2") coVerify(exactly = 1) { postService.allPosts() } }