Skip to content

Commit e3c9ee1

Browse files
committed
refactor!: move error handler to schema
Moves the error handler from the Ktor plugin to the schema itself. The error handler can now be used to map any exception encountered during execution and delegate to the default implementation. BREAKING CHANGE: error handler is no longer supported as part of Ktor plugin configuration
1 parent 5f911e1 commit e3c9ee1

16 files changed

Lines changed: 169 additions & 109 deletions

File tree

docs/content/Plugins/ktor.md

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,12 @@ test it out directly within the browser.
5050
The GraphQL feature is extending the standard [KGraphQL configuration](../Reference/configuration.md) and providing its own
5151
set of configuration as described in the table below.
5252

53-
| Property | Description | Default value |
54-
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|
55-
| endpoint | This specifies what route will be delivering the GraphQL endpoint. When `playground` is enabled, it will use this endpoint also. | `/graphql` |
56-
| context | Refer to example below | |
57-
| wrap | If you want to wrap the route into something before KGraphQL will install the GraphQL route. You can use this wrapper. See example below for a more in depth on how to use it. | |
58-
| errorHandler | Allows interaction with exceptions thrown during GraphQL execution and optional mapping to another one — in particular mapping to `GraphQLError` for serialization in the response. | |
59-
| schema | This is where you are defining your schema. Please refer to [KGraphQL References](../Reference/operations.md) for further documentation on this. | ***required*** |
53+
| Property | Description | Default value |
54+
|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|
55+
| endpoint | This specifies what route will be delivering the GraphQL endpoint. When `playground` is enabled, it will use this endpoint also. | `/graphql` |
56+
| context | Allows to add call-specific information to the GraphQL context, see example below. | |
57+
| wrap | If you want to wrap the route into something before KGraphQL will install the GraphQL route. You can use this wrapper. See example below for a more in depth on how to use it. | |
58+
| schema | This is where you are defining your schema. Please refer to [KGraphQL References](../Reference/operations.md) for further documentation on this. | ***required*** |
6059

6160
### Wrap
6261

@@ -74,7 +73,7 @@ This works great alongside the [context](#context) to provide a context to the K
7473

7574
### Context
7675

77-
To get access to the context
76+
`context` allows to add call-specific information to the GraphQL context:
7877

7978
=== "Example"
8079
```kotlin
@@ -94,35 +93,6 @@ To get access to the context
9493
}
9594
```
9695

97-
### Error Handler
98-
99-
By default, KGraphQL will wrap non-`GraphQLError` exceptions into an `ExecutionException` (when `wrapErrors = true`)
100-
or rethrow them to be handled by Ktor (when `wrapErrors = false`).
101-
102-
The `errorHandler` provides a way to **intercept and transform exceptions** before they are serialized.
103-
It is always defined — by default it simply returns the same exception instance (`{ e -> e }`),
104-
but you can override it to map specific exception types to `GraphQLError` or other `Throwable` instances.
105-
106-
=== "Example"
107-
```kotlin
108-
errorHandler { e ->
109-
when (e) {
110-
is ValidationException -> RequestError(e.message, extensions = mapOf("type" to "VALIDATION_ERROR"))
111-
is DomainException -> RequestError(e.message, extensions = mapOf("type" to "DOMAIN_ERROR"))
112-
is GraphQLError -> e
113-
else -> ExecutionException(e.message ?: "Unknown execution error", cause = e)
114-
}
115-
}
116-
schema {
117-
query("hello") {
118-
resolver { ctx: Context ->
119-
val user = ctx.get<User>()!!
120-
"Hello ${user.name}"
121-
}
122-
}
123-
}
124-
```
125-
12696
## Schema Definition Language (SDL)
12797

12898
The [Schema Definition Language](https://graphql.org/learn/schema/#type-language) (or Type System Definition Language) is a human-readable, language-agnostic

docs/content/Reference/configuration.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
KGraphQL schema allows configuration of the following properties:
22

3-
| Property | Description | Default value |
4-
|--------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
5-
| useDefaultPrettyPrinter | Schema pretty prints JSON responses | `false` |
6-
| useCachingDocumentParser | Schema caches parsed query documents | `true` |
7-
| documentParserCacheMaximumSize | Schema document cache maximum size | `1000` |
8-
| objectMapper | Schema is using Jackson ObjectMapper from this property | result of `jacksonObjectMapper()` from [jackson-kotlin-module](https://github.com/FasterXML/jackson-module-kotlin) |
9-
| acceptSingleValueAsArray | Schema accepts single argument values as singleton list | `true` |
10-
| coroutineDispatcher | Schema is using CoroutineDispatcher from this property | [Dispatchers.Default](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/src/Dispatchers.kt) |
11-
| genericTypeResolver | Schema is using generic type resolver from this property | [GenericTypeResolver.DEFAULT](https://github.com/stuebingerb/KGraphQL/blob/main/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/GenericTypeResolver.kt) |
12-
| wrapErrors | Schema wraps exceptions from resolvers as GraphQLError | |
13-
| introspection | Schema allows introspection (also affects SDL). Introspection can be disabled in production to reduce attack surface. | `true` |
3+
| Property | Description | Default value |
4+
|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
5+
| useDefaultPrettyPrinter | Schema pretty prints JSON responses | `false` |
6+
| useCachingDocumentParser | Schema caches parsed query documents | `true` |
7+
| documentParserCacheMaximumSize | Schema document cache maximum size | `1000` |
8+
| objectMapper | Schema is using Jackson ObjectMapper from this property | result of `jacksonObjectMapper()` from [jackson-kotlin-module](https://github.com/FasterXML/jackson-module-kotlin) |
9+
| acceptSingleValueAsArray | Schema accepts single argument values as singleton list | `true` |
10+
| coroutineDispatcher | Schema is using CoroutineDispatcher from this property | [Dispatchers.Default](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/src/Dispatchers.kt) |
11+
| genericTypeResolver | Schema is using generic type resolver from this property | [GenericTypeResolver.DEFAULT](https://github.com/stuebingerb/KGraphQL/blob/main/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/GenericTypeResolver.kt) |
12+
| wrapErrors | Schema wraps exceptions from resolvers as GraphQLError | `true` |
13+
| introspection | Schema allows introspection (also affects SDL). Introspection can be disabled in production to reduce attack surface. | `true` |
14+
| errorHandler | Allows interaction with exceptions thrown during GraphQL execution and optional mapping to another one — in particular mapping to `GraphQLError` for serialization in the response. | [ErrorHandler](https://github.com/stuebingerb/KGraphQL/blob/main/kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/execution/ErrorHandler.kt) |
1415

1516
=== "Example"
1617
```kotlin

docs/content/Reference/errorHandling.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,4 +221,42 @@ Those re-thrown exceptions could then be handled with the [`StatusPages` Ktor pl
221221
Invalid input: java.lang.IllegalArgumentException: Illegal argument
222222
```
223223

224-
Because thrown exceptions are re-thrown, `wrapErrors = false` will not produce partial responses from thrown exceptions, but resolvers can still return partial responses by calling `Context.raiseError`.
224+
Because thrown exceptions are re-thrown, `wrapErrors = false` will not produce partial responses from thrown exceptions,
225+
but resolvers can still return partial responses by calling `Context.raiseError`. `wrapErrors = false` will also not
226+
invoke a custom error handler. If you want to throw exceptions with custom mapping, use `wrapErrors = true` and re-throw
227+
mapped exceptions from the error handler.
228+
229+
## Error Handler
230+
231+
In KGraphQL, the schema can [configure a custom _error handler_](configuration.md) that is called for each exception
232+
encountered during execution. It can be used to customize default error mapping, and to add additional extensions to
233+
the response.
234+
235+
The error handler is supposed to return a subclass of `GraphQLError`, which is either a `RequestError` or an `ExecutionError`
236+
that will be handled according to the schema. When subclassing from the default `ErrorHandler`, mapping can be delegated
237+
to the standard implementation, completely replaced, or a mixture of both.
238+
239+
=== "Example"
240+
```kotlin
241+
val customErrorHandler = object : ErrorHandler() {
242+
override suspend fun handleException(ctx: Context, node: Execution.Node, exception: Throwable): GraphQLError {
243+
return when (exception) {
244+
is IllegalArgumentException -> ExecutionError(
245+
message = exception.message ?: "",
246+
node = node,
247+
extensions = mapOf("type" to "CUSTOM_ERROR_TYPE")
248+
)
249+
250+
is IllegalAccessException -> RequestError(
251+
message = "You shall not pass!",
252+
node = node.selectionNode,
253+
extensions = mapOf("required_role" to "ADMIN", "reason" to "Gandalf")
254+
)
255+
256+
else -> super.handleException(ctx, node, exception)
257+
}
258+
}
259+
}
260+
```
261+
262+
(!) Exceptions from the error handler itself are *not* wrapped, regardless of the `wrapErrors` configuration.

kgraphql-ktor-stitched/api/kgraphql-ktor-stitched.api

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ public final class com/apurebase/kgraphql/stitched/StitchedKGraphQL {
5656
}
5757

5858
public final class com/apurebase/kgraphql/stitched/schema/configuration/StitchedSchemaConfiguration : com/apurebase/kgraphql/configuration/SchemaConfiguration {
59-
public fun <init> (ZJLcom/fasterxml/jackson/databind/ObjectMapper;ZLkotlinx/coroutines/CoroutineDispatcher;ZZLcom/apurebase/kgraphql/schema/execution/GenericTypeResolver;Lcom/apurebase/kgraphql/schema/execution/ArgumentTransformer;Lcom/apurebase/kgraphql/stitched/schema/execution/RemoteRequestExecutor;Ljava/lang/String;)V
60-
public synthetic fun <init> (ZJLcom/fasterxml/jackson/databind/ObjectMapper;ZLkotlinx/coroutines/CoroutineDispatcher;ZZLcom/apurebase/kgraphql/schema/execution/GenericTypeResolver;Lcom/apurebase/kgraphql/schema/execution/ArgumentTransformer;Lcom/apurebase/kgraphql/stitched/schema/execution/RemoteRequestExecutor;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
59+
public fun <init> (ZJLcom/fasterxml/jackson/databind/ObjectMapper;ZLkotlinx/coroutines/CoroutineDispatcher;ZZLcom/apurebase/kgraphql/schema/execution/GenericTypeResolver;Lcom/apurebase/kgraphql/schema/execution/ArgumentTransformer;Lcom/apurebase/kgraphql/schema/execution/ErrorHandler;Lcom/apurebase/kgraphql/stitched/schema/execution/RemoteRequestExecutor;Ljava/lang/String;)V
60+
public synthetic fun <init> (ZJLcom/fasterxml/jackson/databind/ObjectMapper;ZLkotlinx/coroutines/CoroutineDispatcher;ZZLcom/apurebase/kgraphql/schema/execution/GenericTypeResolver;Lcom/apurebase/kgraphql/schema/execution/ArgumentTransformer;Lcom/apurebase/kgraphql/schema/execution/ErrorHandler;Lcom/apurebase/kgraphql/stitched/schema/execution/RemoteRequestExecutor;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
6161
public final fun getLocalUrl ()Ljava/lang/String;
6262
public final fun getRemoteExecutor ()Lcom/apurebase/kgraphql/stitched/schema/execution/RemoteRequestExecutor;
6363
}

kgraphql-ktor-stitched/src/main/kotlin/com/apurebase/kgraphql/stitched/schema/configuration/StitchedSchemaConfiguration.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.apurebase.kgraphql.stitched.schema.configuration
33
import com.apurebase.kgraphql.ExperimentalAPI
44
import com.apurebase.kgraphql.configuration.SchemaConfiguration
55
import com.apurebase.kgraphql.schema.execution.ArgumentTransformer
6+
import com.apurebase.kgraphql.schema.execution.ErrorHandler
67
import com.apurebase.kgraphql.schema.execution.GenericTypeResolver
78
import com.apurebase.kgraphql.stitched.schema.execution.RemoteRequestExecutor
89
import com.fasterxml.jackson.databind.ObjectMapper
@@ -23,6 +24,7 @@ class StitchedSchemaConfiguration(
2324
introspection: Boolean = true,
2425
genericTypeResolver: GenericTypeResolver,
2526
argumentTransformer: ArgumentTransformer,
27+
errorHandler: ErrorHandler,
2628
val remoteExecutor: RemoteRequestExecutor,
2729
val localUrl: String?
2830
) : SchemaConfiguration(
@@ -34,5 +36,6 @@ class StitchedSchemaConfiguration(
3436
wrapErrors,
3537
introspection,
3638
genericTypeResolver,
37-
argumentTransformer
39+
argumentTransformer,
40+
errorHandler
3841
)

kgraphql-ktor-stitched/src/main/kotlin/com/apurebase/kgraphql/stitched/schema/dsl/StitchedSchemaConfigurationDSL.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ open class StitchedSchemaConfigurationDSL : SchemaConfigurationDSL() {
2727
introspection = introspection,
2828
genericTypeResolver = genericTypeResolver,
2929
argumentTransformer = RemoteArgumentTransformer(objectMapper, genericTypeResolver),
30+
errorHandler = errorHandler,
3031
remoteExecutor = requireNotNull(remoteExecutor) { "Remote executor not defined" },
3132
localUrl = localUrl
3233
)

kgraphql-ktor/api/kgraphql-ktor.api

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ public final class com/apurebase/kgraphql/GraphQL {
77
public final class com/apurebase/kgraphql/GraphQL$Configuration : com/apurebase/kgraphql/schema/dsl/SchemaConfigurationDSL {
88
public fun <init> ()V
99
public final fun context (Lkotlin/jvm/functions/Function2;)V
10-
public final fun errorHandler (Lkotlin/jvm/functions/Function1;)V
1110
public final fun getEndpoint ()Ljava/lang/String;
1211
public final fun getPlayground ()Z
1312
public final fun schema (Lkotlin/jvm/functions/Function1;)V

0 commit comments

Comments
 (0)