-
Notifications
You must be signed in to change notification settings - Fork 329
Description
Context
Spring Version: 3.4.1
Spring Cloud: 2024.0.0
Kotlin Version: 2.0.21
Kotlin coroutines: 1.10.1
Whole dependency list
- org.springframework.boot:spring-boot-starter-graphql
- com.graphql-java:graphql-java-extended-scalars:20.2
- org.springframework.data:spring-data-commons
- org.springframework.cloud:spring-cloud-starter-loadbalancer
- org.springframework.boot:spring-boot-starter-oauth2-client
- org.springframework.security:spring-security-oauth2-client
- org.keycloak:keycloak-admin-client
- org.springframework.boot:spring-boot-starter-security
- io.micrometer:micrometer-tracing
- io.zipkin.reporter2:zipkin-reporter-brave
- org.springframework.boot:spring-boot-starter-actuator
- io.micrometer:micrometer-tracing-bridge-brave
- com.fasterxml.jackson.module:jackson-module-kotlin
- io.github.wimdeblauwe:error-handling-spring-boot-starter
- org.springframework.boot:spring-boot-starter-oauth2-resource-server
- org.springframework.cloud:spring-cloud-starter-netflix-eureka-client
- io.github.thibaultmeyer:cuid:2.0.3
- org.springframework.boot:spring-boot-starter-webflux
- org.jetbrains.kotlinx:kotlinx-coroutines-core
- org.jetbrains.kotlinx:kotlinx-coroutines-reactor
- org.springframework.cloud:spring-cloud-starter-circuitbreaker-reactor-resilience4j
- io.projectreactor.kotlin:reactor-kotlin-extensions
- org.springframework:spring-aspects
- com.github.ben-manes.caffeine:caffeine:3.1.8
Description
I have the following schema (only relevant things are put here)
type Subscription {
id: CUID!
owner: User!
name: String!
}
type User {
id: UUID!
username: String!
firstName: String!
lastName: String!
email: String!
locale: String!
}
scalar CUID
subscription(id: CUID!): Subscription
I have the following controller and DTOs:
data class SubscriptionDTO(
val id: CUID,
val ownerId: UUID,
val name: String,
val limits: SubscriptionLimitsDTO,
val paymentInfo: PaymentInfoDTO,
val active: Boolean,
val deleteWarningSent: Boolean
)
data class UserDTO(
val id: UUID,
val username: String,
val firstName: String,
val lastName: String,
val email: String,
val locale: String
)
@QueryMapping
suspend fun subscription(
@Argument("id") id: CUID,
principal: JwtAuthenticationToken
): SubscriptionDTO {
logger.info("Called sub")
logger.debug("Returning subscription of id {}", id.toString())
return authorityManager.withSubscriptionAuthenticationContext(
Permission.READ_SUBSCRIPTION,
id,
principal
) {
it.subscription
}
}
@SchemaMapping(typeName = "Subscription", field = "owner")
fun owner(subscription: SubscriptionDTO): UserDTO {
logger.info("Called 1")
val result = keycloakService.getUser(subscription.ownerId).orElse(null) ?: UserDTO(
id = UUID.randomUUID(),
username = "unknown",
firstName = "Unknown",
lastName = "Unknown",
email = "[email protected]",
locale = "en"
)
logger.info(
"Resolving owner ({}) to UserDTO. Result is: {}",
subscription.ownerId,
result.toString()
)
return result
}
And this is my scalar config:
@Configuration
class GraphQLConverters {
@Bean
fun graphqlSourceBuilderCustomizer(): RuntimeWiringConfigurer {
return RuntimeWiringConfigurer { wiringBuilder ->
wiringBuilder
.scalar(ExtendedScalars.UUID) // Register custom UUID scalar
.scalar(cuidScalar()) // Register custom CUID scalar
}
}
// Define CUID Scalar
private fun cuidScalar() = GraphQLScalarType.newScalar()
.name("CUID")
.description("Custom CUID scalar")
.coercing(object : Coercing<CUID, String> {
override fun serialize(dataFetcherResult: Any): String {
return (dataFetcherResult as CUID).toString()
}
override fun parseValue(input: Any): CUID {
return CUID.fromString(input.toString())
}
override fun parseLiteral(input: Any): CUID {
if (input is StringValue) {
return CUID.fromString(input.value)
}
throw CoercingParseLiteralException("Invalid value for CUID")
}
})
.build()
}
My test query is the following (yes the subscription exists)
query {
subscription(id: "cy9sw9aebqj734knmj3kic6i") {
id
name
owner {
id
username
}
}
}
Howerver I get an error: The field at path '/subscription/owner/id' was declared as a non null type, but the code involved in retrieving data has wrongly returned a null value. The graphql specification requires that the parent field be set to null, or if that is non nullable that it bubble up null to its parent and so on. The non-nullable type is 'UUID' within parent type 'User'
I figured the problem is because the SubscriptionDTO is returned with the owner being UUID (so no child fields), and the Schema Mapping value is ignored.
However I can see from the logs, that the mapper is indeed called:
2025-01-13T16:40:27.690Z INFO 1 --- [graphql-service] [or-http-epoll-3] [678541fbd50a9698e543be612d92e520-4f11b1c7df9aeb05] h.g.g.c.SubscriptionController : Called sub
2025-01-13T16:40:27.729Z INFO 1 --- [graphql-service] [or-http-epoll-1] [678541fbd50a9698e543be612d92e520-cd679ba828bfd6a3] h.g.g.c.SubscriptionController : Called 1
2025-01-13T16:40:27.762Z INFO 1 --- [graphql-service] [or-http-epoll-1] [678541fbd50a9698e543be612d92e520-cd679ba828bfd6a3] h.g.g.c.SubscriptionController : Resolving owner (f2b762ec-1b9e-4145-ab5b-a634e2b32491) to UserDTO. Result is: UserDTO(id=f2b762ec-1b9e-4145-ab5b-a634e2b32491, [email protected], firstName=Test, lastName=Test, [email protected], locale=hu)
I don't know if you need any further logs or information, if so I can provide it in a reply. However I've tried many ideas, but none of them worked, so I'm reaching out here, because this is possibly a bug.