Skip to content

Commit 8fbec9d

Browse files
authored
[client] support @include and @Skip directives (ExpediaGroup#1341)
1 parent a747e3d commit 8fbec9d

File tree

22 files changed

+282
-15
lines changed

22 files changed

+282
-15
lines changed

plugins/client/graphql-kotlin-client-generator/src/main/kotlin/com/expediagroup/graphql/plugin/client/generator/types/generateGraphQLObjectTypeSpec.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ internal fun generateGraphQLObjectTypeSpec(
6868

6969
val constructorParameter = ParameterSpec.builder(propertySpec.name, propertySpec.type)
7070
val className = propertySpec.type as? ClassName
71-
if (className != null && context.enumClassToTypeSpecs.keys.contains(className)) {
71+
if (propertySpec.type.isNullable) {
72+
constructorParameter.defaultValue("null")
73+
} else if (className != null && context.enumClassToTypeSpecs.keys.contains(className)) {
7274
constructorParameter.defaultValue("%T.%N", className, className.member(UNKNOWN_VALUE))
7375
}
7476
constructorBuilder.addParameter(constructorParameter.build())

plugins/client/graphql-kotlin-client-generator/src/main/kotlin/com/expediagroup/graphql/plugin/client/generator/types/generatePropertySpecs.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import com.squareup.kotlinpoet.ParameterizedTypeName
3030
import com.squareup.kotlinpoet.PropertySpec
3131
import com.squareup.kotlinpoet.TypeName
3232
import graphql.Directives.DeprecatedDirective
33+
import graphql.Directives.IncludeDirective
34+
import graphql.Directives.SkipDirective
3335
import graphql.language.Field
3436
import graphql.language.FieldDefinition
3537
import graphql.language.NonNullType
@@ -59,7 +61,10 @@ internal fun generatePropertySpecs(
5961
throw MissingArgumentException(context.operationName, objectName, selectedField.name, missingRequiredArguments)
6062
}
6163

62-
val kotlinFieldType = generateTypeName(context, fieldDefinition.type, selectedField.selectionSet)
64+
val optional = selectedField.directives.any {
65+
it.name == SkipDirective.name || it.name == IncludeDirective.name
66+
}
67+
val kotlinFieldType = generateTypeName(context, fieldDefinition.type, selectedField.selectionSet, optional = optional)
6368
val fieldName = selectedField.alias ?: fieldDefinition.name
6469

6570
val propertySpecBuilder = PropertySpec.builder(fieldName, kotlinFieldType)

plugins/client/graphql-kotlin-client-generator/src/main/kotlin/com/expediagroup/graphql/plugin/client/generator/types/generateTypeName.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,13 @@ import kotlinx.serialization.Serializable
5151
/**
5252
* Generate [TypeName] reference to a Kotlin class representation of an underlying GraphQL type.
5353
*/
54-
internal fun generateTypeName(context: GraphQLClientGeneratorContext, graphQLType: Type<*>, selectionSet: SelectionSet? = null): TypeName {
55-
val nullable = graphQLType !is NonNullType
54+
internal fun generateTypeName(
55+
context: GraphQLClientGeneratorContext,
56+
graphQLType: Type<*>,
57+
selectionSet: SelectionSet? = null,
58+
optional: Boolean = false
59+
): TypeName {
60+
val nullable = optional || graphQLType !is NonNullType
5661

5762
return when (graphQLType) {
5863
is NonNullType -> generateTypeName(context, graphQLType.type, selectionSet)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
query IncludeSkipDirectivesQuery($includeCondition: Boolean!, $skipCondition: Boolean!) {
2+
enumQuery @include(if: $includeCondition)
3+
scalarQuery @skip(if: $skipCondition) {
4+
count
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.expediagroup.graphql.generated
2+
3+
import com.expediagroup.graphql.client.Generated
4+
import com.expediagroup.graphql.client.types.GraphQLClientRequest
5+
import com.expediagroup.graphql.generated.enums.CustomEnum
6+
import com.expediagroup.graphql.generated.includeskipdirectivesquery.ScalarWrapper
7+
import kotlin.Boolean
8+
import kotlin.String
9+
import kotlin.reflect.KClass
10+
11+
public const val INCLUDE_SKIP_DIRECTIVES_QUERY: String =
12+
"query IncludeSkipDirectivesQuery(${'$'}includeCondition: Boolean!, ${'$'}skipCondition: Boolean!) {\n enumQuery @include(if: ${'$'}includeCondition)\n scalarQuery @skip(if: ${'$'}skipCondition) {\n count\n }\n}"
13+
14+
@Generated
15+
public class IncludeSkipDirectivesQuery(
16+
public override val variables: IncludeSkipDirectivesQuery.Variables
17+
) : GraphQLClientRequest<IncludeSkipDirectivesQuery.Result> {
18+
public override val query: String = INCLUDE_SKIP_DIRECTIVES_QUERY
19+
20+
public override val operationName: String = "IncludeSkipDirectivesQuery"
21+
22+
public override fun responseType(): KClass<IncludeSkipDirectivesQuery.Result> =
23+
IncludeSkipDirectivesQuery.Result::class
24+
25+
@Generated
26+
public data class Variables(
27+
public val includeCondition: Boolean,
28+
public val skipCondition: Boolean
29+
)
30+
31+
@Generated
32+
public data class Result(
33+
/**
34+
* Query that returns enum value
35+
*/
36+
public val enumQuery: CustomEnum? = null,
37+
/**
38+
* Query that returns wrapper object with all supported scalar types
39+
*/
40+
public val scalarQuery: ScalarWrapper? = null
41+
)
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.expediagroup.graphql.generated.enums
2+
3+
import com.expediagroup.graphql.client.Generated
4+
import com.fasterxml.jackson.`annotation`.JsonEnumDefaultValue
5+
import com.fasterxml.jackson.`annotation`.JsonProperty
6+
import kotlin.Deprecated
7+
8+
/**
9+
* Custom enum description
10+
*/
11+
@Generated
12+
public enum class CustomEnum {
13+
/**
14+
* First enum value
15+
*/
16+
ONE,
17+
/**
18+
* Third enum value
19+
*/
20+
@Deprecated(message = "only goes up to two")
21+
THREE,
22+
/**
23+
* Second enum value
24+
*/
25+
TWO,
26+
/**
27+
* Lowercase enum value
28+
*/
29+
@JsonProperty("four")
30+
FOUR,
31+
/**
32+
* This is a default enum value that will be used when attempting to deserialize unknown value.
33+
*/
34+
@JsonEnumDefaultValue
35+
__UNKNOWN_VALUE,
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.expediagroup.graphql.generated.includeskipdirectivesquery
2+
3+
import com.expediagroup.graphql.client.Generated
4+
import kotlin.Int
5+
6+
/**
7+
* Wrapper that holds all supported scalar types
8+
*/
9+
@Generated
10+
public data class ScalarWrapper(
11+
/**
12+
* A signed 32-bit nullable integer value
13+
*/
14+
public val count: Int? = null
15+
)

plugins/client/graphql-kotlin-client-generator/src/test/data/generator/input_lists/InputListQuery.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ public class InputListQuery(
3131
/**
3232
* Query accepting list input
3333
*/
34-
public val listInputQuery: String?
34+
public val listInputQuery: String? = null
3535
)
3636
}

plugins/client/graphql-kotlin-client-generator/src/test/data/generator/scalar_typealias/scalaraliasquery/ScalarWrapper.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ public data class ScalarWrapper(
1616
/**
1717
* Custom scalar of UUID
1818
*/
19-
public val custom: UUID?
19+
public val custom: UUID? = null
2020
)

plugins/client/graphql-kotlin-client-generator/src/test/data/jackson/custom_scalars/customscalarquery/ScalarWrapper.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ public data class ScalarWrapper(
2121
*/
2222
@JsonSerialize(converter = UUIDToAnyConverter::class)
2323
@JsonDeserialize(converter = AnyToUUIDConverter::class)
24-
public val custom: UUID?,
24+
public val custom: UUID? = null,
2525
/**
2626
* List of custom scalar UUIDs
2727
*/
2828
@JsonSerialize(contentConverter = UUIDToAnyConverter::class)
2929
@JsonDeserialize(contentConverter = AnyToUUIDConverter::class)
30-
public val customList: List<UUID>?,
30+
public val customList: List<UUID>? = null,
3131
/**
3232
* Custom scalar of Locale
3333
*/

plugins/client/graphql-kotlin-client-generator/src/test/data/jackson/object/complexobjectquery/ComplexObject.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public data class ComplexObject(
2323
* Optional value
2424
* Second line of the description
2525
*/
26-
public val optional: String?,
26+
public val optional: String? = null,
2727
/**
2828
* Some additional details
2929
*/

plugins/client/graphql-kotlin-client-generator/src/test/data/kotlinx/multiple_queries/firstquery/ComplexObject.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public data class ComplexObject(
2525
* Optional value
2626
* Second line of the description
2727
*/
28-
public val optional: String?,
28+
public val optional: String? = null,
2929
/**
3030
* Some additional details
3131
*/

plugins/client/graphql-kotlin-client-generator/src/test/data/kotlinx/multiple_queries/firstquery/ScalarWrapper.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ public data class ScalarWrapper(
1616
/**
1717
* A signed 32-bit nullable integer value
1818
*/
19-
public val count: Int?,
19+
public val count: Int? = null,
2020
/**
2121
* Custom scalar of UUID
2222
*/
2323
@Serializable(with = UUIDSerializer::class)
24-
public val custom: UUID?,
24+
public val custom: UUID? = null,
2525
/**
2626
* ID represents unique identifier that is not intended to be human readable
2727
*/

plugins/client/graphql-kotlin-client-generator/src/test/data/kotlinx/multiple_queries/secondquery/ScalarWrapper.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ public data class ScalarWrapper(
1616
/**
1717
* A signed 32-bit nullable integer value
1818
*/
19-
public val count: Int?,
19+
public val count: Int? = null,
2020
/**
2121
* Custom scalar of UUID
2222
*/
2323
@Serializable(with = UUIDSerializer::class)
24-
public val custom: UUID?,
24+
public val custom: UUID? = null,
2525
/**
2626
* ID represents unique identifier that is not intended to be human readable
2727
*/

plugins/client/graphql-kotlin-client-generator/src/test/data/kotlinx/object/complexobjectquery/ComplexObject.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public data class ComplexObject(
2525
* Optional value
2626
* Second line of the description
2727
*/
28-
public val optional: String?,
28+
public val optional: String? = null,
2929
/**
3030
* Some additional details
3131
*/
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2+
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateSDLTask
3+
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLGenerateTestClientTask
4+
5+
buildscript {
6+
repositories {
7+
mavenLocal {
8+
content {
9+
includeGroup("com.expediagroup")
10+
}
11+
}
12+
}
13+
14+
val graphQLKotlinVersion = System.getenv("GRAPHQL_KOTLIN_VERSION") ?: "5.0.0-SNAPSHOT"
15+
dependencies {
16+
classpath("com.expediagroup:graphql-kotlin-gradle-plugin:$graphQLKotlinVersion")
17+
}
18+
}
19+
20+
plugins {
21+
id("org.springframework.boot") version "2.5.5"
22+
kotlin("jvm") version "1.5.31"
23+
kotlin("plugin.spring") version "1.5.31"
24+
}
25+
26+
apply(plugin = "com.expediagroup.graphql")
27+
28+
group = "com.expediagroup"
29+
version = "0.0.1-SNAPSHOT"
30+
31+
repositories {
32+
mavenCentral()
33+
mavenLocal {
34+
content {
35+
includeGroup("com.expediagroup")
36+
}
37+
}
38+
}
39+
40+
val graphQLKotlinVersion = System.getenv("GRAPHQL_KOTLIN_VERSION") ?: "5.0.0-SNAPSHOT"
41+
val icuVersion = System.getenv("ICU_VERSION") ?: "69.1"
42+
val junitVersion = System.getenv("JUNIT_VERSION") ?: "5.7.2"
43+
val kotlinVersion = System.getenv("KOTLIN_VERSION") ?: "1.5.31"
44+
val springBootVersion = System.getenv("SPRINGBOOT_VERSION") ?: "2.5.5"
45+
dependencies {
46+
implementation("com.expediagroup:graphql-kotlin-hooks-provider:$graphQLKotlinVersion")
47+
implementation("com.expediagroup:graphql-kotlin-spring-client:$graphQLKotlinVersion")
48+
implementation("com.expediagroup:graphql-kotlin-spring-server:$graphQLKotlinVersion")
49+
implementation("com.ibm.icu:icu4j:$icuVersion")
50+
testImplementation(kotlin("test-junit5", kotlinVersion))
51+
testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
52+
testImplementation("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
53+
testImplementation("org.springframework.boot:spring-boot-starter-test:$springBootVersion")
54+
}
55+
56+
tasks.withType<KotlinCompile> {
57+
kotlinOptions {
58+
freeCompilerArgs = listOf("-Xjsr305=strict")
59+
jvmTarget = "11"
60+
}
61+
}
62+
63+
tasks.withType<Test> {
64+
useJUnitPlatform()
65+
}
66+
67+
val graphqlGenerateSDL by tasks.getting(GraphQLGenerateSDLTask::class) {
68+
packages.set(listOf("com.expediagroup.directives"))
69+
}
70+
val graphqlGenerateTestClient by tasks.getting(GraphQLGenerateTestClientTask::class) {
71+
packageName.set("com.expediagroup.directives.generated")
72+
schemaFile.set(graphqlGenerateSDL.schemaFile)
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rootProject.name = "include-skip-test"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.expediagroup.directives
2+
3+
import org.springframework.boot.autoconfigure.SpringBootApplication
4+
import org.springframework.boot.runApplication
5+
6+
@SpringBootApplication
7+
class Application
8+
9+
fun main(args: Array<String>) {
10+
runApplication<Application>(*args)
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.expediagroup.directives
2+
3+
import com.expediagroup.graphql.server.operations.Query
4+
import org.springframework.stereotype.Component
5+
import java.util.UUID
6+
7+
@Component
8+
class SimpleQuery : Query {
9+
10+
fun simpleQuery(): String = UUID.randomUUID().toString()
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
graphql:
2+
packages:
3+
- "com.expediagroup.directives"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.expediagroup.directives
2+
3+
import com.expediagroup.directives.generated.IncludeSkipQuery
4+
import com.expediagroup.graphql.client.spring.GraphQLWebClient
5+
import kotlinx.coroutines.runBlocking
6+
import org.junit.jupiter.api.Test
7+
import org.springframework.boot.test.context.SpringBootTest
8+
import org.springframework.boot.web.server.LocalServerPort
9+
import java.util.UUID
10+
import kotlin.test.assertNotNull
11+
import kotlin.test.assertNull
12+
13+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
14+
class ApplicationTest(@LocalServerPort private val port: Int) {
15+
16+
@Test
17+
fun `verify include and skip directives are honored by client`(): Unit = runBlocking {
18+
val client = GraphQLWebClient(url = "http://localhost:$port/graphql")
19+
20+
val skippedQuery = IncludeSkipQuery(variables = IncludeSkipQuery.Variables(
21+
includeCondition = false,
22+
skipCondition = true
23+
))
24+
25+
val response = client.execute(skippedQuery)
26+
val simpleResponse = response.data?.simpleQuery
27+
assertNotNull(simpleResponse)
28+
assertNotNull(UUID.fromString(simpleResponse))
29+
30+
val included = response.data?.included
31+
assertNull(included)
32+
val skipped = response.data?.skipped
33+
assertNull(skipped)
34+
35+
val includeQuery = IncludeSkipQuery(variables = IncludeSkipQuery.Variables(
36+
includeCondition = true,
37+
skipCondition = false
38+
))
39+
40+
val nonNullResponse = client.execute(includeQuery)
41+
val simpleResponseNonNull = nonNullResponse.data?.simpleQuery
42+
assertNotNull(simpleResponseNonNull)
43+
assertNotNull(UUID.fromString(simpleResponseNonNull))
44+
45+
val includedNonNull = nonNullResponse.data?.included
46+
assertNotNull(includedNonNull)
47+
assertNotNull(UUID.fromString(includedNonNull))
48+
val skippedNonNull = nonNullResponse.data?.skipped
49+
assertNotNull(skippedNonNull)
50+
assertNotNull(UUID.fromString(skippedNonNull))
51+
}
52+
}

0 commit comments

Comments
 (0)