ExpediaGroup/graphql-kotlin

@ShareableDirective causes schema generation error when applied to class used as both input and output

jdsatlin opened this issue · 0 comments

Hi GraphQL Kotlin devs. I appreciate your work, and thanks for taking a look at this issue. Please let me know if there is any more details or information that would be helpful to include.

Library Version
tested and reproduced in graphql-kotlin-spring-server:6.6.0 & graphql-kotlin-spring-server:7.0.2 (latest at time of issue creation)

Describe the bug
Using GraphQL Kotlin Spring Server and a Federation V2 model, schema generation fails when a type with the @ShareableDiretive is referenced as both input and output of a GraphQL operation. When GraphQL Kotlin is generating the schema for that type used, for example like

@ShareableDirective
data class ExampleTypeRepro(val latitude: Double, val longitude: Double)

@Component
class ExampleQueryRepro: Query {

    suspend fun exampleQueryRepro(exampleTypeAsInput: ExampleTypeRepro): ExampleTypeRepro {
        return ExampleTypeRepro(0.0, 0.0)
    }
}

it automatically generates both the input and output types, and attempts to apply the @shareable directive to both input and output type, resulting in an invalid schema like

type ExampleTypeRepro @shareable {
    latitude: Float!
    longitude: Float!
}

input ExampleTypeReproInput @shareable {
    latitude: Float!
    longitude: Float!
}

where @shareable can only be applied to output types and output fields.

As a workaround the user can explicitly define both the input and output types as separate Kotlin classes, but that creates an otherwise unnecessary proliferation of types where without the shareable directive GraphQL Kotlin would be able to automatically create the appropriate and valid GraphQL types from a single Kotlin class.

To Reproduce
Clone https://github.com/jdsatlin/shareable-bug-repro featuring both a working workaround example and the bug case already configured and attempt to run it.
Alternatively: Set up a new GraphQL Kotlin Spring server with federation enabled, and these types defined under the package configured for schema generation

@ShareableDirective
data class ExampleTypeRepro(val latitude: Double, val longitude: Double)

@Component
class ExampleQueryRepro: Query {

    suspend fun exampleQueryRepro(exampleTypeAsInput: ExampleTypeRepro): ExampleTypeRepro {
        return ExampleTypeRepro(0.0, 0.0)
    }
}

and run that.
Result: see an error during schema generation that has an ultimate root cause exception along the lines of:

Caused by: com.expediagroup.graphql.generator.exceptions.InvalidDirectiveLocationException: Directive shareable was specified in unsupported INPUT_OBJECT location (class com.graphql.kotlin.bug.repro.shareablebugrepro.schema.ExampleTypeRepro). This directive can only be applied on one of the supported locations: FIELD_DEFINITION, OBJECT.
	at com.expediagroup.graphql.generator.internal.types.GenerateDirectiveKt.generateDirectives(generateDirective.kt:51) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.types.GenerateDirectiveKt.generateDirectives$default(generateDirective.kt:36) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.types.GenerateInputObjectKt.generateInputObject(generateInputObject.kt:43) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt.getGraphQLType(generateGraphQLType.kt:105) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt.access$getGraphQLType(generateGraphQLType.kt:1) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt$objectFromReflection$1.invoke(generateGraphQLType.kt:67) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt$objectFromReflection$1.invoke(generateGraphQLType.kt:66) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.state.TypesCache.buildIfNotUnderConstruction$graphql_kotlin_schema_generator(TypesCache.kt:150) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt.objectFromReflection(generateGraphQLType.kt:66) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt.generateGraphQLType(generateGraphQLType.kt:45) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.types.GenerateArgumentKt.generateArgument(generateArgument.kt:51) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.types.GenerateFunctionKt.generateFunction(generateFunction.kt:50) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.types.GenerateFunctionKt.generateFunction$default(generateFunction.kt:34) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.internal.types.GenerateQueryKt.generateQueries(generateQuery.kt:43) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.SchemaGenerator.generateSchema(SchemaGenerator.kt:80) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.federation.FederatedSchemaGenerator.generateSchema(FederatedSchemaGenerator.kt:45) ~[graphql-kotlin-federation-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.SchemaGenerator.generateSchema$default(SchemaGenerator.kt:58) ~[graphql-kotlin-schema-generator-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.generator.federation.ToFederatedSchemaKt.toFederatedSchema(toFederatedSchema.kt:44) ~[graphql-kotlin-federation-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.server.spring.FederatedSchemaAutoConfiguration.schema(FederatedSchemaAutoConfiguration.kt:95) ~[graphql-kotlin-spring-server-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.server.spring.FederatedSchemaAutoConfiguration$$SpringCGLIB$$0.CGLIB$schema$0(<generated>) ~[graphql-kotlin-spring-server-7.0.2.jar:7.0.2]
	at com.expediagroup.graphql.server.spring.FederatedSchemaAutoConfiguration$$SpringCGLIB$$FastClass$$1.invoke(<generated>) ~[graphql-kotlin-spring-server-7.0.2.jar:7.0.2]
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:258) ~[spring-core-6.1.4.jar:6.1.4]
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-6.1.4.jar:6.1.4]
	at com.expediagroup.graphql.server.spring.FederatedSchemaAutoConfiguration$$SpringCGLIB$$0.schema(<generated>) ~[graphql-kotlin-spring-server-7.0.2.jar:7.0.2]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:140) ~[spring-beans-6.1.4.jar:6.1.4]

Expected behavior
GraphQL Kotlin should be able to generate schema input and output types from a single Kotlin class or field of a Kotlin class annotated with @ShareableDirective and only apply @shareable in the schema on the appropriate locations of the output type.