Skip to content

Commit 490709f

Browse files
authored
[plugin] expose timeout config for downloadSDL/introspectSchema tasks (ExpediaGroup#775)
* [plugin] expose timeout configuration for downloadSDL/introspectSchema tasks Expose new read/connect timeout configuration for downloadSDL and introspectSchema tasks (and corresponding MOJOs). Example configuration: ```kotlin graphql { client { endpoint = "http://localhost:8080/graphql" packageName = "com.example.generated" timeout { // Connect timeout in milliseconds connect = 5_000 // Read timeout in milliseconds read = 15_000 } } } ``` ```xml <plugin> <groupId>com.expediagroup</groupId> <artifactId>graphql-kotlin-maven-plugin</artifactId> <version>${graphql-kotlin.version}</version> <executions> <execution> <goals> <goal>introspect-schema</goal> </goals> <configuration> <endpoint>http://localhost:8080/graphql</endpoint> <!-- optional configuration below --> <timeoutConfiguration> <!-- timeout values in milliseconds --> <connect>5000</connect> <read>15000</read> </timeoutConfiguration> </configuration> </execution> </executions> </plugin> ``` Resolves: ExpediaGroup#745 * update timeout values for test It looks like GH Actions sometimes take a bit longer to execute so bumping up the response delay from 1s to 10s. * disable parallel maven integration tests * enable streaming mvn logs to std out * unique integration test maven project names
1 parent 078b21c commit 490709f

File tree

27 files changed

+703
-94
lines changed

27 files changed

+703
-94
lines changed

docs/plugins/gradle-plugin.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ graphql {
4646
converters["UUID"] = ScalarConverterMapping("java.util.UUID", "com.example.UUIDScalarConverter")
4747
// List of query files to be processed.
4848
queryFiles.add(file("${project.projectDir}/src/main/resources/queries/MyQuery.graphql"))
49+
// Timeout configuration
50+
timeout {
51+
// Connect timeout in milliseconds
52+
connect = 5_000
53+
// Read timeout in milliseconds
54+
read = 15_000
55+
}
4956
}
5057
}
5158
```
@@ -68,6 +75,7 @@ and could be used as an alternative to `graphqlIntrospectSchema` to generate inp
6875
| -------- | ---- | -------- | ----------- |
6976
| `endpoint` | String | yes | Target GraphQL server SDL endpoint that will be used to download schema.<br/>**Command line property is**: `endpoint`. |
7077
| `headers` | Map<String, Any> | | Optional HTTP headers to be specified on a SDL request. |
78+
| `timeoutConfig` | TimeoutConfig | | Timeout configuration(in milliseconds) to download schema from SDL endpoint before we cancel the request.<br/>**Default value are:** connect timeout = 5_000, read timeout = 15_000.<br/>|
7179

7280
### graphqlGenerateClient
7381

@@ -121,6 +129,7 @@ should be used to generate input for the subsequent `graphqlGenerateClient` task
121129
| -------- | ---- | -------- | ----------- |
122130
| `endpoint` | String | yes | Target GraphQL server endpoint that will be used to execute introspection queries.<br/>**Command line property is**: `endpoint`. |
123131
| `headers` | Map<String, Any> | | Optional HTTP headers to be specified on an introspection query. |
132+
| `timeoutConfig` | TimeoutConfig | | Timeout configuration(in milliseconds) to download schema from SDL endpoint before we cancel the request.<br/>**Default value are:** connect timeout = 5_000, read timeout = 15_000.<br/>|
124133

125134
## Examples
126135

@@ -307,6 +316,8 @@ the GraphQL client code based on the provided query.
307316

308317
```kotlin
309318
// build.gradle.kts
319+
import com.expediagroup.graphql.plugin.config.TimeoutConfig
320+
import com.expediagroup.graphql.plugin.generator.ScalarConverterMapping
310321
import com.expediagroup.graphql.plugin.gradle.graphql
311322

312323
graphql {
@@ -318,6 +329,10 @@ graphql {
318329
headers["X-Custom-Header"] = "My-Custom-Header"
319330
converters["UUID"] = ScalarConverterMapping("java.util.UUID", "com.example.UUIDScalarConverter")
320331
queryFiles.add(file("${project.projectDir}/src/main/resources/queries/MyQuery.graphql"))
332+
timeout {
333+
connect = 10_000
334+
read = 30_000
335+
}
321336
}
322337
}
323338
```
@@ -326,12 +341,15 @@ Above configuration is equivalent to the following
326341

327342
```kotlin
328343
// build.gradle.kts
344+
import com.expediagroup.graphql.plugin.config.TimeoutConfig
345+
import com.expediagroup.graphql.plugin.generator.ScalarConverterMapping
329346
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLDownloadSDLTask
330347
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLIntrospectSchemaTask
331348

332349
val graphqlDownloadSDL by tasks.getting(GraphQLDownloadSDLTask::class) {
333350
endpoint.set("http://localhost:8080/sdl")
334351
headers.put("X-Custom-Header", "My-Custom-Header")
352+
timeoutConfig.set(TimeoutConfig(connect = 10_000, read = 30_000))
335353
}
336354
val graphqlGenerateClient by tasks.getting(GraphQLGenerateClientTask::class) {
337355
packageName.set("com.example.generated")

docs/plugins/maven-plugin.md

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ goal provides limited functionality by itself and instead should be used to gene
2929
| -------- | ---- | -------- | ----------- |
3030
| `endpoint` | String | yes | Target GraphQL server SDL endpoint that will be used to download schema.<br/>**User property is**: `graphql.endpoint`. |
3131
| `headers` | Map<String, Any> | | Optional HTTP headers to be specified on a SDL request.
32+
| `timeoutConfiguration` | TimeoutConfiguration | | Optional timeout configuration (in milliseconds) to download schema from SDL endpoint before we cancel the request.<br/>**Default values are:** connect timeout = 5000, read timeout = 15000.<br/> |
33+
34+
**Parameter Details**
35+
36+
* *timeoutConfiguration* - Timeout configuration that allows you to specify connect and read timeout values in milliseconds.
37+
38+
```xml
39+
<timeoutConfiguration>
40+
<!-- timeout values in milliseconds -->
41+
<connect>1000</connect>
42+
<read>30000</read>
43+
</timeoutConfiguration>
44+
```
3245

3346
### generate-client
3447

@@ -124,6 +137,19 @@ should be used to generate input for the subsequent `generate-client` goal.
124137
| -------- | ---- | -------- | ----------- |
125138
| `endpoint` | String | yes | Target GraphQL server endpoint that will be used to execute introspection queries.<br/>**User property is**: `graphql.endpoint`. |
126139
| `headers` | Map<String, Any> | | Optional HTTP headers to be specified on an introspection query. |
140+
| `timeoutConfiguration` | TimeoutConfiguration | | Optional timeout configuration(in milliseconds) to execute introspection query before we cancel the request.<br/>**Default values are:** connect timeout = 5000, read timeout = 15000.<br/> |
141+
142+
**Parameter Details**
143+
144+
* *timeoutConfiguration* - Timeout configuration that allows you to specify connect and read timeout values in milliseconds.
145+
146+
```xml
147+
<timeoutConfiguration>
148+
<!-- timeout values in milliseconds -->
149+
<connect>1000</connect>
150+
<read>30000</read>
151+
</timeoutConfiguration>
152+
```
127153

128154
## Examples
129155

@@ -374,9 +400,6 @@ the GraphQL client code based on the provided query.
374400
<schemaFile>${project.build.directory}/schema.graphql</schemaFile>
375401
<!-- optional configuration below -->
376402
<allowDeprecatedFields>true</allowDeprecatedFields>
377-
<headers>
378-
<X-Custom-Header>My-Custom-Header</X-Custom-Header>
379-
</headers>
380403
<converters>
381404
<!-- custom scalar UUID type -->
382405
<UUID>
@@ -387,6 +410,14 @@ the GraphQL client code based on the provided query.
387410
<converter>com.example.UUIDScalarConverter</converter>
388411
</UUID>
389412
</converters>
413+
<headers>
414+
<X-Custom-Header>My-Custom-Header</X-Custom-Header>
415+
</headers>
416+
<timeoutConfiguration>
417+
<!-- timeout values in milliseconds -->
418+
<connect>1000</connect>
419+
<read>30000</read>
420+
</timeoutConfiguration>
390421
<queryFiles>
391422
<queryFile>${project.basedir}/src/main/resources/queries/MyQuery.graphql</queryFile>
392423
</queryFiles>

plugins/graphql-kotlin-gradle-plugin/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ graphql {
3838
converters["UUID"] = ScalarConverterMapping("java.util.UUID", "com.example.UUIDScalarConverter")
3939
// List of query files to be processed.
4040
queryFiles.add(file("${project.projectDir}/src/main/resources/queries/MyQuery.graphql"))
41+
timeout {
42+
// Connect timeout in milliseconds
43+
connect = 5_000
44+
// Read timeout in milliseconds
45+
read = 15_000
46+
}
4147
}
4248
}
4349
```
@@ -61,6 +67,7 @@ and could be used as an alternative to `graphqlIntrospectSchema` to generate inp
6167
| -------- | ---- | -------- | ----------- |
6268
| `endpoint` | String | yes | Target GraphQL server SDL endpoint that will be used to download schema.<br/>**Command line property is**: `endpoint`. |
6369
| `headers` | Map<String, Any> | | Optional HTTP headers to be specified on a SDL request. |
70+
| `timeoutConfig` | TimeoutConfig | | Optional timeout configuration(in milliseconds) to download schema from SDL endpoint before we cancel the request.<br/>**Default value are:** connect timeout = 5_000, read timeout = 15_000.<br/>|
6471

6572
### graphqlGenerateClient
6673

@@ -114,6 +121,7 @@ should be used to generate input for the subsequent `graphqlGenerateClient` task
114121
| -------- | ---- | -------- | ----------- |
115122
| `endpoint` | String | yes | Target GraphQL server endpoint that will be used to execute introspection queries.<br/>**Command line property is**: `endpoint`. |
116123
| `headers` | Map<String, Any> | | Optional HTTP headers to be specified on an introspection query. |
124+
| `timeoutConfig` | TimeoutConfig | | Optional timeout configuration(in milliseconds) to execute introspection query before we cancel the request.<br/>**Default value are:** connect timeout = 5_000, read timeout = 15_000.<br/>|
117125

118126
## Documentation
119127

plugins/graphql-kotlin-gradle-plugin/src/main/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLGradlePlugin.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,14 @@ class GraphQLGradlePlugin : Plugin<Project> {
6767
val introspectSchemaTask = project.tasks.named(INTROSPECT_SCHEMA_TASK_NAME, GraphQLIntrospectSchemaTask::class.java).get()
6868
introspectSchemaTask.endpoint.convention(project.provider { extension.clientExtension.endpoint })
6969
introspectSchemaTask.headers.convention(project.provider { extension.clientExtension.headers })
70+
introspectSchemaTask.timeoutConfig.convention(project.provider { extension.clientExtension.timeoutConfig })
7071
generateClientTask.dependsOn(introspectSchemaTask.path)
7172
generateClientTask.schemaFile.convention(introspectSchemaTask.outputFile)
7273
} else if (extension.clientExtension.sdlEndpoint != null) {
7374
val downloadSDLTask = project.tasks.named(DOWNLOAD_SDL_TASK_NAME, GraphQLDownloadSDLTask::class.java).get()
7475
downloadSDLTask.endpoint.convention(project.provider { extension.clientExtension.sdlEndpoint })
7576
downloadSDLTask.headers.convention(project.provider { extension.clientExtension.headers })
77+
downloadSDLTask.timeoutConfig.convention(project.provider { extension.clientExtension.timeoutConfig })
7678
generateClientTask.dependsOn(downloadSDLTask.path)
7779
generateClientTask.schemaFile.convention(downloadSDLTask.outputFile)
7880
} else {

plugins/graphql-kotlin-gradle-plugin/src/main/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLPluginExtension.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.expediagroup.graphql.plugin.gradle
1818

19+
import com.expediagroup.graphql.plugin.config.TimeoutConfig
1920
import com.expediagroup.graphql.plugin.generator.ScalarConverterMapping
2021
import java.io.File
2122

@@ -54,4 +55,11 @@ open class GraphQLPluginClientExtension {
5455
var converters: MutableMap<String, ScalarConverterMapping> = mutableMapOf()
5556
/** List of query files to be processed. */
5657
var queryFiles: MutableList<File> = mutableListOf()
58+
59+
/** Connect and read timeout configuration for executing introspection query/download schema */
60+
internal val timeoutConfig: TimeoutConfig = TimeoutConfig()
61+
62+
fun timeout(config: TimeoutConfig.() -> Unit = {}) {
63+
timeoutConfig.apply(config)
64+
}
5765
}

plugins/graphql-kotlin-gradle-plugin/src/main/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLDownloadSDLTask.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.expediagroup.graphql.plugin.gradle.tasks
1818

19+
import com.expediagroup.graphql.plugin.config.TimeoutConfig
1920
import com.expediagroup.graphql.plugin.downloadSchema
2021
import kotlinx.coroutines.runBlocking
2122
import org.gradle.api.DefaultTask
@@ -49,6 +50,13 @@ open class GraphQLDownloadSDLTask : DefaultTask() {
4950
@Input
5051
val headers: MapProperty<String, Any> = project.objects.mapProperty(String::class.java, Any::class.java)
5152

53+
/**
54+
* Timeout configuration that specifies maximum amount of time (in milliseconds) to connect and download schema from SDL endpoint before we cancel the request.
55+
* Defaults to Ktor CIO engine defaults (5 seconds for connect timeout and 15 seconds for read timeout).
56+
*/
57+
@Input
58+
val timeoutConfig: Property<TimeoutConfig> = project.objects.property(TimeoutConfig::class.java)
59+
5260
@OutputFile
5361
val outputFile: Provider<RegularFile> = project.layout.buildDirectory.file("schema.graphql")
5462

@@ -57,6 +65,7 @@ open class GraphQLDownloadSDLTask : DefaultTask() {
5765
description = "Download schema in SDL format from target endpoint."
5866

5967
headers.convention(emptyMap())
68+
timeoutConfig.convention(TimeoutConfig())
6069
}
6170

6271
/**
@@ -67,7 +76,7 @@ open class GraphQLDownloadSDLTask : DefaultTask() {
6776
fun downloadSDLAction() {
6877
logger.debug("starting download SDL task against ${endpoint.get()}")
6978
runBlocking {
70-
val schema = downloadSchema(endpoint = endpoint.get(), httpHeaders = headers.get())
79+
val schema = downloadSchema(endpoint = endpoint.get(), httpHeaders = headers.get(), timeoutConfig = timeoutConfig.get())
7180
val outputFile = outputFile.get().asFile
7281
outputFile.writeText(schema)
7382
}

plugins/graphql-kotlin-gradle-plugin/src/main/kotlin/com/expediagroup/graphql/plugin/gradle/tasks/GraphQLIntrospectSchemaTask.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.expediagroup.graphql.plugin.gradle.tasks
1818

19+
import com.expediagroup.graphql.plugin.config.TimeoutConfig
1920
import com.expediagroup.graphql.plugin.introspectSchema
2021
import kotlinx.coroutines.runBlocking
2122
import org.gradle.api.DefaultTask
@@ -49,6 +50,13 @@ open class GraphQLIntrospectSchemaTask : DefaultTask() {
4950
@Input
5051
val headers: MapProperty<String, Any> = project.objects.mapProperty(String::class.java, Any::class.java)
5152

53+
/**
54+
* Timeout configuration that specifies maximum amount of time (in milliseconds) to connect and execute introspection query before we cancel the request.
55+
* Defaults to Ktor CIO engine defaults (5 seconds for connect timeout and 15 seconds for read timeout).
56+
*/
57+
@Input
58+
val timeoutConfig: Property<TimeoutConfig> = project.objects.property(TimeoutConfig::class.java)
59+
5260
@OutputFile
5361
val outputFile: Provider<RegularFile> = project.layout.buildDirectory.file("schema.graphql")
5462

@@ -57,6 +65,7 @@ open class GraphQLIntrospectSchemaTask : DefaultTask() {
5765
description = "Run introspection query against target GraphQL endpoint and save schema locally."
5866

5967
headers.convention(emptyMap())
68+
timeoutConfig.convention(TimeoutConfig())
6069
}
6170

6271
/**
@@ -67,7 +76,7 @@ open class GraphQLIntrospectSchemaTask : DefaultTask() {
6776
fun introspectSchemaAction() {
6877
logger.debug("starting introspection task against ${endpoint.get()}")
6978
runBlocking {
70-
val schema = introspectSchema(endpoint = endpoint.get(), httpHeaders = headers.get())
79+
val schema = introspectSchema(endpoint = endpoint.get(), httpHeaders = headers.get(), timeoutConfig = timeoutConfig.get())
7180
outputFile.get().asFile.writeText(schema)
7281
}
7382
logger.debug("successfully created GraphQL schema from introspection result")

plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLDownloadSDLTaskIT.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,29 @@ class GraphQLDownloadSDLTaskIT : GraphQLGradlePluginAbstractIT() {
7777
assertEquals(TaskOutcome.SUCCESS, result.task(":$DOWNLOAD_SDL_TASK_NAME")?.outcome)
7878
assertTrue(File(tempDir.toFile(), "build/schema.graphql").exists())
7979
}
80+
81+
@Test
82+
fun `apply the gradle plugin and execute downloadSDL with timeout`(@TempDir tempDir: Path) {
83+
val testProjectDirectory = tempDir.toFile()
84+
WireMock.reset()
85+
WireMock.stubFor(stubSdlEndpoint(delay = 10_000))
86+
87+
val buildFileContents =
88+
"""
89+
val graphqlDownloadSDL by tasks.getting(GraphQLDownloadSDLTask::class) {
90+
endpoint.set("${wireMockServer.baseUrl()}/sdl")
91+
timeoutConfig.set(TimeoutConfig(connect = 100, read = 100))
92+
}
93+
""".trimIndent()
94+
testProjectDirectory.generateBuildFile(buildFileContents)
95+
96+
val result = GradleRunner.create()
97+
.withProjectDir(testProjectDirectory)
98+
.withPluginClasspath()
99+
.withArguments(DOWNLOAD_SDL_TASK_NAME)
100+
.buildAndFail()
101+
102+
assertEquals(TaskOutcome.FAILED, result.task(":$DOWNLOAD_SDL_TASK_NAME")?.outcome)
103+
assertTrue(result.output.contains("Timed out waiting for 100 ms", ignoreCase = true))
104+
}
80105
}

plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLGradlePluginAbstractIT.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,23 @@ abstract class GraphQLGradlePluginAbstractIT {
5151
WireMock.stubFor(stubGraphQLResponse())
5252
}
5353

54-
fun stubSdlEndpoint(): MappingBuilder = WireMock.get("/sdl")
55-
.withResponse(content = testSchema, contentType = "text/plain")
54+
fun stubSdlEndpoint(delay: Int? = null): MappingBuilder = WireMock.get("/sdl")
55+
.withResponse(content = testSchema, contentType = "text/plain", delay = delay)
5656

57-
fun stubIntrospectionResult(): MappingBuilder = WireMock.post("/graphql")
57+
fun stubIntrospectionResult(delay: Int? = null): MappingBuilder = WireMock.post("/graphql")
5858
.withRequestBody(ContainsPattern("IntrospectionQuery"))
59-
.withResponse(content = introspectionResult)
59+
.withResponse(content = introspectionResult, delay = delay)
6060

61-
fun stubGraphQLResponse(): MappingBuilder = WireMock.post("/graphql")
61+
fun stubGraphQLResponse(delay: Int? = null): MappingBuilder = WireMock.post("/graphql")
6262
.withRequestBody(ContainsPattern("JUnitQuery"))
63-
.withResponse(content = testResponse)
63+
.withResponse(content = testResponse, delay = delay)
6464

65-
private fun MappingBuilder.withResponse(content: String, contentType: String = "application/json") = this.willReturn(
65+
private fun MappingBuilder.withResponse(content: String, contentType: String = "application/json", delay: Int? = null) = this.willReturn(
6666
WireMock.aResponse()
6767
.withStatus(200)
6868
.withHeader("Content-Type", contentType)
6969
.withBody(content)
70+
.withFixedDelay(delay ?: 0)
7071
)
7172

7273
fun loadResource(resourceName: String) = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName)?.use {
@@ -82,6 +83,7 @@ abstract class GraphQLGradlePluginAbstractIT {
8283
val buildFileContents =
8384
"""
8485
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
86+
import com.expediagroup.graphql.plugin.config.TimeoutConfig
8587
import com.expediagroup.graphql.plugin.generator.ScalarConverterMapping
8688
import com.expediagroup.graphql.plugin.gradle.graphql
8789
import com.expediagroup.graphql.plugin.gradle.tasks.GraphQLDownloadSDLTask

plugins/graphql-kotlin-gradle-plugin/src/test/kotlin/com/expediagroup/graphql/plugin/gradle/GraphQLIntrospectSchemaTaskIT.kt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,29 @@ class GraphQLIntrospectSchemaTaskIT : GraphQLGradlePluginAbstractIT() {
7676
assertEquals(TaskOutcome.SUCCESS, result.task(":$INTROSPECT_SCHEMA_TASK_NAME")?.outcome)
7777
assertTrue(File(testProjectDirectory, "build/schema.graphql").exists())
7878
}
79+
80+
@Test
81+
fun `apply the gradle plugin and execute introspectSchema with timeout`(@TempDir tempDir: Path) {
82+
val testProjectDirectory = tempDir.toFile()
83+
WireMock.reset()
84+
WireMock.stubFor(stubIntrospectionResult(delay = 10_000))
85+
86+
val buildFileContents =
87+
"""
88+
val graphqlIntrospectSchema by tasks.getting(GraphQLIntrospectSchemaTask::class) {
89+
endpoint.set("${wireMockServer.baseUrl()}/graphql")
90+
timeoutConfig.set(TimeoutConfig(connect = 100, read = 100))
91+
}
92+
""".trimIndent()
93+
testProjectDirectory.generateBuildFile(buildFileContents)
94+
95+
val result = GradleRunner.create()
96+
.withProjectDir(testProjectDirectory)
97+
.withPluginClasspath()
98+
.withArguments(INTROSPECT_SCHEMA_TASK_NAME)
99+
.buildAndFail()
100+
101+
assertEquals(TaskOutcome.FAILED, result.task(":$INTROSPECT_SCHEMA_TASK_NAME")?.outcome)
102+
assertTrue(result.output.contains("Timed out waiting for 100 ms", ignoreCase = true))
103+
}
79104
}

0 commit comments

Comments
 (0)