Blazebit / blaze-persistence

Rich Criteria API for JPA providers
https://persistence.blazebit.com
Apache License 2.0
738 stars 89 forks source link

When use dgs-framework with mutation, can't found primary constructor of entity view #1694

Open debop opened 1 year ago

debop commented 1 year ago

Description

I ported blaze-persistence examples\spring-data-dgs to Kotlin

Query is success, but mutation is failed. No primary constructor found for class class CatCreateView

Expected behavior

dgs deserialize Entity View Interface.

Actual behavior

when run mutation, dgs deserialize Entity View Interface (CatCreateView)

Caused by: com.netflix.graphql.dgs.exceptions.DgsInvalidInputArgumentException: No primary constructor found for class class com.hpcnt.workshop.blaze.dgs.domain.view.CatCreateView
    at com.netflix.graphql.dgs.internal.DefaultInputObjectMapper.mapToKotlinObject(DefaultInputObjectMapper.kt:43)
    at com.netflix.graphql.dgs.internal.method.InputObjectMapperConverter.convert(InputObjectMapperConverter.kt:38)
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41)
    ... 78 common frames omitted

-->

    override fun <T : Any> mapToKotlinObject(inputMap: Map<String, *>, targetClass: KClass<T>): T {
        val constructor = targetClass.primaryConstructor
            ?: throw DgsInvalidInputArgumentException("No primary constructor found for class $targetClass")
    override fun convert(source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any {
        @Suppress("unchecked_cast")
        val mapInput = source as Map<String, *>
        return if (KotlinDetector.isKotlinType(targetType.type)) {
            inputObjectMapper.mapToKotlinObject(mapInput, targetType.type.kotlin)  // targetType.type.kotlin -> CatCreateView interface
        } else {
            inputObjectMapper.mapToJavaObject(mapInput, targetType.type)
        }
    }

Steps to reproduce

  1. Entity Views
@EntityView(Cat::class)
interface CatSimpleView {

    @get:IdMapping
    val id: Long?

    val name: String?
}

@EntityView(Cat::class)
@UpdatableEntityView
interface CatUpdateView: CatSimpleView {

    override var name: String?

    var age: Int?
}

@CreatableEntityView
@EntityView(Cat::class)
interface CatSimpleCreateView: CatUpdateView {

    var owner: PersonIdView?
}

@CreatableEntityView
@EntityView(Cat::class)
interface CatCreateView: CatSimpleCreateView {

    var kittens: MutableSet<CatSimpleCreateView>?

}

GraphQL Schema

type Mutation {
    createCat(cat: CatCreateViewInput): Int
}

Test case

    @Test
    fun `request mutation for createCat`() {
        val catName = "Test"
        var requestGraphQL = """
            mutation {
                createCat(
                    cat: {
                        name: "$catName"
                        age: 1
                        owner: {id: 1}
                        kittens: [
                            { name: "Kitten 1", age: 1, owner: {id: 1} }
                        ]
                    }
                )
            }
            """.trimIndent()

        val headers = HttpHeaders().apply {
            add("content-type", "application/graphql")
        }
        var response = restTemplate.postForEntity<JsonNode>("/graphql", HttpEntity(requestGraphQL, headers))
    }

Sorry I used my library

Environment

Version: 1.6.8
JPA-Provider: Hibernate 5.6.8.Final
DBMS: H2
Application Server: Spring Boot 2.7.8 DGS Framework : 5.5.1

beikov commented 1 year ago

Thanks for the report. I guess we might have to add a test that exercises the whole thing when the entity view is actually a Kotlin type, as the InputObjectMapper currently doesn't handle this case. Also see https://github.com/Blazebit/blaze-persistence/pull/1686