Skip to content

Commit d8eb4ce

Browse files
authored
Handle exception on subscription initialization (#1940)
### 📝 Description In case of Subscription initiation failure, for example exception thrown after some custom authentication/authorisation mechanism throws exception, error was not passed through as response body, because of exception: ``` java.lang.NullPointerException: graphQL.execute(input) …<Flow<ExecutionResult>>() must not be null at com.expediagroup.graphql.server.execution.GraphQLRequestHandler.executeSubscription(GraphQLRequestHandler.kt:167) ``` ### 🔗 Related Issues -
1 parent 06435c9 commit d8eb4ce

File tree

2 files changed

+93
-9
lines changed

2 files changed

+93
-9
lines changed

servers/graphql-kotlin-server/src/main/kotlin/com/expediagroup/graphql/server/execution/GraphQLRequestHandler.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import kotlinx.coroutines.async
4444
import kotlinx.coroutines.awaitAll
4545
import kotlinx.coroutines.flow.Flow
4646
import kotlinx.coroutines.flow.catch
47+
import kotlinx.coroutines.flow.flowOf
4748
import kotlinx.coroutines.flow.map
4849
import kotlinx.coroutines.future.await
4950
import kotlinx.coroutines.supervisorScope
@@ -61,6 +62,7 @@ open class GraphQLRequestHandler(
6162
.firstOrNull(Instrumentation::isBatchDataLoaderInstrumentation)
6263
?.javaClass
6364
}
65+
6466
instrumentation.isBatchDataLoaderInstrumentation() -> instrumentation.javaClass
6567
else -> null
6668
}
@@ -81,6 +83,7 @@ open class GraphQLRequestHandler(
8183
val batchGraphQLContext = graphQLContext + getBatchContext(1, dataLoaderRegistry)
8284
execute(graphQLRequest, batchGraphQLContext, dataLoaderRegistry)
8385
}
86+
8487
is GraphQLBatchRequest -> {
8588
if (graphQLRequest.containsMutation()) {
8689
val batchGraphQLContext = graphQLContext + getBatchContext(1, dataLoaderRegistry)
@@ -145,9 +148,11 @@ open class GraphQLRequestHandler(
145148
DataLoaderLevelDispatchedInstrumentation::class.java -> mapOf(
146149
ExecutionLevelDispatchedState::class to ExecutionLevelDispatchedState(batchSize)
147150
)
151+
148152
DataLoaderSyncExecutionExhaustedInstrumentation::class.java -> mapOf(
149153
SyncExecutionExhaustedState::class to SyncExecutionExhaustedState(batchSize, dataLoaderRegistry)
150154
)
155+
151156
else -> emptyMap<Any, Any>()
152157
}
153158
return batchContext
@@ -162,9 +167,12 @@ open class GraphQLRequestHandler(
162167
): Flow<GraphQLResponse<*>> {
163168
val dataLoaderRegistry = dataLoaderRegistryFactory?.generate(graphQLContext)
164169
val input = graphQLRequest.toExecutionInput(graphQLContext, dataLoaderRegistry)
170+
val executionResult = graphQL.execute(input)
171+
172+
val resultFlow: Flow<ExecutionResult> = executionResult
173+
.getData<Flow<ExecutionResult>?>() ?: flowOf(executionResult)
165174

166-
return graphQL.execute(input)
167-
.getData<Flow<ExecutionResult>>()
175+
return resultFlow
168176
.map { result -> result.toGraphQLResponse() }
169177
.catch { throwable ->
170178
val error = throwable.toGraphQLError()

servers/graphql-kotlin-server/src/test/kotlin/com/expediagroup/graphql/server/execution/GraphQLRequestHandlerTest.kt

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@ import com.expediagroup.graphql.dataloader.instrumentation.level.DataLoaderLevel
2222
import com.expediagroup.graphql.dataloader.instrumentation.syncexhaustion.DataLoaderSyncExecutionExhaustedInstrumentation
2323
import com.expediagroup.graphql.generator.SchemaGeneratorConfig
2424
import com.expediagroup.graphql.generator.TopLevelObject
25+
import com.expediagroup.graphql.generator.execution.FlowSubscriptionExecutionStrategy
2526
import com.expediagroup.graphql.generator.extensions.toGraphQLContext
27+
import com.expediagroup.graphql.generator.hooks.FlowSubscriptionSchemaGeneratorHooks
2628
import com.expediagroup.graphql.generator.toSchema
2729
import com.expediagroup.graphql.server.types.GraphQLBatchRequest
2830
import com.expediagroup.graphql.server.types.GraphQLBatchResponse
2931
import com.expediagroup.graphql.server.types.GraphQLRequest
3032
import com.expediagroup.graphql.server.types.GraphQLResponse
33+
import com.expediagroup.graphql.server.types.GraphQLServerError
3134
import graphql.ExecutionInput
3235
import graphql.GraphQL
3336
import graphql.GraphQLContext
@@ -38,24 +41,32 @@ import graphql.schema.DataFetchingEnvironment
3841
import graphql.schema.GraphQLSchema
3942
import io.mockk.every
4043
import io.mockk.mockk
41-
import kotlinx.coroutines.runBlocking
42-
import org.dataloader.DataLoader
43-
import org.dataloader.DataLoaderFactory
44-
import org.junit.jupiter.api.Test
4544
import java.util.concurrent.CompletableFuture
4645
import kotlin.random.Random
4746
import kotlin.test.assertEquals
4847
import kotlin.test.assertNotNull
4948
import kotlin.test.assertNull
49+
import kotlinx.coroutines.flow.Flow
50+
import kotlinx.coroutines.flow.first
51+
import kotlinx.coroutines.flow.flow
52+
import kotlinx.coroutines.flow.flowOf
53+
import kotlinx.coroutines.runBlocking
54+
import org.dataloader.DataLoader
55+
import org.dataloader.DataLoaderFactory
56+
import org.junit.jupiter.api.Test
5057

5158
class GraphQLRequestHandlerTest {
5259

5360
private val testSchema: GraphQLSchema = toSchema(
54-
config = SchemaGeneratorConfig(supportedPackages = listOf("com.expediagroup.graphql.server.execution")),
61+
config = SchemaGeneratorConfig(
62+
supportedPackages = listOf("com.expediagroup.graphql.server.execution"),
63+
hooks = FlowSubscriptionSchemaGeneratorHooks(),
64+
),
5565
queries = listOf(TopLevelObject(BasicQuery())),
56-
mutations = listOf(TopLevelObject(BasicMutation()))
66+
mutations = listOf(TopLevelObject(BasicMutation())),
67+
subscriptions = listOf(TopLevelObject(BasicSubscription())),
5768
)
58-
private val testGraphQL: GraphQL = GraphQL.newGraphQL(testSchema).build()
69+
private val testGraphQL: GraphQL = GraphQL.newGraphQL(testSchema).subscriptionExecutionStrategy(FlowSubscriptionExecutionStrategy()).build()
5970
private val graphQLRequestHandler = GraphQLRequestHandler(testGraphQL)
6071

6172
private fun getBatchingRequestHandler(instrumentation: Instrumentation): GraphQLRequestHandler =
@@ -317,6 +328,59 @@ class GraphQLRequestHandlerTest {
317328
}
318329
}
319330

331+
@Test
332+
fun `execute graphQL subscription`() {
333+
val response = runBlocking {
334+
val context = emptyMap<String, Any>().toGraphQLContext()
335+
val request = GraphQLRequest(
336+
query = "subscription { users(name: \"Jane Doe\") { name } }",
337+
)
338+
graphQLRequestHandler.executeSubscription(request, context).first()
339+
}
340+
341+
assertNotNull(response.data as? Map<*, *>) { data ->
342+
assertNotNull(data["users"] as? Map<*, *>) { user ->
343+
assertEquals("Jane Doe", user["name"])
344+
}
345+
}
346+
assertNull(response.errors)
347+
assertNull(response.extensions)
348+
}
349+
350+
@Test
351+
fun `execute graphQL subscription with error`() {
352+
val response = runBlocking {
353+
val context = emptyMap<String, Any>().toGraphQLContext()
354+
val request = GraphQLRequest(
355+
query = "subscription { withFlowError { name } }",
356+
)
357+
graphQLRequestHandler.executeSubscription(request, context).first()
358+
}
359+
360+
assertNotNull(response.errors as List<GraphQLServerError>) { errors ->
361+
assertNotNull(errors[0]) { error ->
362+
assertEquals("Subscription failure", error.message)
363+
}
364+
}
365+
}
366+
367+
@Test
368+
fun `execute graphQL subscription with error on init`() {
369+
val response = runBlocking {
370+
val context = emptyMap<String, Any>().toGraphQLContext()
371+
val request = GraphQLRequest(
372+
query = "subscription { withInitError { name } }",
373+
)
374+
graphQLRequestHandler.executeSubscription(request, context).first()
375+
}
376+
377+
assertNotNull(response.errors as List<GraphQLServerError>) { errors ->
378+
assertNotNull(errors[0]) { error ->
379+
assertEquals("Exception while fetching data (/withInitError) : Subscription failure", error.message)
380+
}
381+
}
382+
}
383+
320384
data class User(val id: Int, val name: String)
321385

322386
class BasicQuery {
@@ -338,4 +402,16 @@ class GraphQLRequestHandlerTest {
338402
class BasicMutation {
339403
fun addUser(name: String): User = User(Random.nextInt(), name)
340404
}
405+
406+
class BasicSubscription {
407+
fun users(name: String): Flow<User> = flowOf(User(Random.nextInt(), name))
408+
409+
fun withFlowError(): Flow<User> = flow {
410+
throw Exception("Subscription failure")
411+
}
412+
413+
fun withInitError(): Flow<User> {
414+
throw Exception("Subscription failure")
415+
}
416+
}
341417
}

0 commit comments

Comments
 (0)