Skip to content

Commit d372345

Browse files
author
Dariusz Kuc
authored
[generator] support directive array/vararg parameters (#1431)
1 parent b5ae1c0 commit d372345

File tree

8 files changed

+64
-9
lines changed

8 files changed

+64
-9
lines changed

generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/internal/extensions/kClassExtensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ internal fun KClass<*>.isValidAdditionalType(inputType: Boolean): Boolean = !(in
8080

8181
internal fun KClass<*>.isEnum(): Boolean = this.isSubclassOf(Enum::class)
8282

83-
internal fun KClass<*>.isListType(): Boolean = this.isSubclassOf(List::class)
83+
internal fun KClass<*>.isListType(isDirective: Boolean = false): Boolean = this.isSubclassOf(List::class) || (isDirective && this.java.isArray)
8484

8585
@Throws(CouldNotGetNameOfKClassException::class)
8686
internal fun KClass<*>.getSimpleName(isInputClass: Boolean = false): String {

generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/internal/state/TypesCache.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ internal class TypesCache(private val supportedPackages: List<String>) : Closeab
7777
}
7878

7979
private fun generateCacheKey(type: KType, typeInfo: GraphQLKTypeMetadata): TypesCacheKey {
80-
if (type.getKClass().isListType()) {
81-
return TypesCacheKey(type, typeInfo.inputType)
80+
if (type.getKClass().isListType(typeInfo.isDirective)) {
81+
return TypesCacheKey(type, typeInfo.inputType, isDirective = typeInfo.isDirective)
8282
}
8383

8484
val customTypeAnnotation = typeInfo.fieldAnnotations.getCustomTypeAnnotation()
@@ -125,7 +125,7 @@ internal class TypesCache(private val supportedPackages: List<String>) : Closeab
125125
val kClass = type.getKClass()
126126

127127
when {
128-
kClass.isListType() -> null
128+
kClass.isListType(cacheKey.isDirective) -> null
129129
kClass.isSubclassOf(Enum::class) -> kClass.getSimpleName()
130130
isTypeNotSupported(type) -> throw TypeNotSupportedException(type, supportedPackages)
131131
else -> type.getSimpleName(cacheKey.inputType)
@@ -136,7 +136,7 @@ internal class TypesCache(private val supportedPackages: List<String>) : Closeab
136136
private fun isTypeNotSupported(type: KType): Boolean = supportedPackages.none { type.qualifiedName.startsWith(it) }
137137

138138
internal fun buildIfNotUnderConstruction(kClass: KClass<*>, typeInfo: GraphQLKTypeMetadata, build: (KClass<*>) -> GraphQLType): GraphQLType {
139-
if (kClass.isListType()) {
139+
if (kClass.isListType(typeInfo.isDirective)) {
140140
return build(kClass)
141141
}
142142

generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/internal/state/TypesCacheKey.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,6 @@ import kotlin.reflect.KType
2121
internal data class TypesCacheKey(
2222
val type: KType,
2323
val inputType: Boolean = false,
24-
val name: String? = null
24+
val name: String? = null,
25+
val isDirective: Boolean = false
2526
)

generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/internal/types/GraphQLKTypeMetadata.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ package com.expediagroup.graphql.generator.internal.types
2323
internal data class GraphQLKTypeMetadata(
2424
val inputType: Boolean = false,
2525
val fieldName: String? = null,
26-
val fieldAnnotations: List<Annotation> = emptyList()
26+
val fieldAnnotations: List<Annotation> = emptyList(),
27+
val isDirective: Boolean = false
2728
)

generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/internal/types/generateDirective.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ private fun getDirective(generator: SchemaGenerator, directiveInfo: DirectiveInf
108108

109109
private fun generateDirectiveArgument(prop: KProperty<*>, generator: SchemaGenerator): GraphQLArgument {
110110
val propertyName = prop.name
111-
val type = generateGraphQLType(generator, prop.returnType)
111+
val type = generateGraphQLType(generator, prop.returnType, GraphQLKTypeMetadata(inputType = true, isDirective = true))
112112

113113
// default directive argument values are unsupported
114114
// https://github.com/ExpediaGroup/graphql-kotlin/issues/53

generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/internal/types/generateGraphQLType.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ private fun getGraphQLType(
8080

8181
return when {
8282
kClass.isEnum() -> @Suppress("UNCHECKED_CAST") (generateEnum(generator, kClass as KClass<Enum<*>>))
83-
kClass.isListType() -> generateList(generator, type, typeInfo)
83+
kClass.isListType(typeInfo.isDirective) -> generateList(generator, type, typeInfo)
8484
kClass.isUnion(typeInfo.fieldAnnotations) -> generateUnion(
8585
generator,
8686
kClass,

generator/graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/internal/types/GenerateDirectiveTest.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import kotlin.reflect.KClass
3232
import kotlin.reflect.KFunction
3333
import kotlin.test.assertEquals
3434
import kotlin.test.assertNotEquals
35+
import kotlin.test.assertNotNull
3536
import kotlin.test.assertTrue
3637

3738
class GenerateDirectiveTest {
@@ -78,6 +79,12 @@ class GenerateDirectiveTest {
7879
@GraphQLDirective
7980
annotation class RepeatableDirective(val value: String)
8081

82+
@GraphQLDirective
83+
annotation class DirectiveWithArray(val args: Array<String>)
84+
85+
@GraphQLDirective
86+
annotation class DirectiveWithVarArg(vararg val args: String)
87+
8188
class MyClass {
8289

8390
fun noAnnotation(string: String) = string
@@ -112,6 +119,12 @@ class GenerateDirectiveTest {
112119
@RepeatableDirective("bar")
113120
@RepeatableDirective("baz")
114121
fun repeatedDirectives(string: String) = string
122+
123+
@DirectiveWithArray(args = ["foo", "bar"])
124+
fun directiveWithArray(string: String) = string
125+
126+
@DirectiveWithVarArg(args = ["foo", "bar"])
127+
fun directiveWithVararg(string: String) = string
115128
}
116129

117130
data class MyClassWithConstructorArgs(
@@ -266,6 +279,36 @@ class GenerateDirectiveTest {
266279
assertEquals("baz", repeatableDirectiveResult[2].getArgument("value")?.argumentValue?.value)
267280
}
268281

282+
@Test
283+
fun `directives that accept argument arrays are supported`() {
284+
val directiveResult = generateDirectives(basicGenerator, MyClass::directiveWithArray, DirectiveLocation.FIELD_DEFINITION)
285+
assertEquals(1, directiveResult.size)
286+
assertEquals("directiveWithArray", directiveResult[0].name)
287+
val argument = directiveResult[0].getArgument("args")
288+
assertNotNull(argument)
289+
assertTrue(argument.argumentValue.value?.javaClass?.isArray == true)
290+
val arrayArgs = argument.argumentValue.value as? Array<*>
291+
assertNotNull(arrayArgs)
292+
assertEquals(2, arrayArgs.size)
293+
assertEquals("foo", arrayArgs[0])
294+
assertEquals("bar", arrayArgs[1])
295+
}
296+
297+
@Test
298+
fun `directives that accept vararg argument are supported`() {
299+
val directiveResult = generateDirectives(basicGenerator, MyClass::directiveWithVararg, DirectiveLocation.FIELD_DEFINITION)
300+
assertEquals(1, directiveResult.size)
301+
assertEquals("directiveWithVarArg", directiveResult[0].name)
302+
val argument = directiveResult[0].getArgument("args")
303+
assertNotNull(argument)
304+
assertTrue(argument.argumentValue.value?.javaClass?.isArray == true)
305+
val arrayArgs = argument.argumentValue.value as? Array<*>
306+
assertNotNull(arrayArgs)
307+
assertEquals(2, arrayArgs.size)
308+
assertEquals("foo", arrayArgs[0])
309+
assertEquals("bar", arrayArgs[1])
310+
}
311+
269312
companion object {
270313
@AfterAll
271314
fun cleanUp(generateDirectiveTest: GenerateDirectiveTest) {

website/docs/schema-generator/customizing-schemas/directives.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
---
23
id: directives
34
title: Directives
@@ -199,3 +200,12 @@ directive @directiveWithIgnoredArgs(
199200
string: String!
200201
) on ...
201202
```
203+
204+
## Limitations
205+
206+
GraphQL specification allows usage of any valid input objects as directive arguments. Since we rely on Kotlin annotation
207+
functionality to define our custom directives, we are limited in what can be used as annotation parameter - only primitives (or scalars),
208+
Strings, Enums, other annotations or an array of any of the above are supported.
209+
210+
Support for input objects can be added by providing that object representation as an annotation class and then adding support
211+
for it through custom schema generator hooks.

0 commit comments

Comments
 (0)