Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 7 additions & 37 deletions docs/content/Plugins/ktor.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,12 @@ test it out directly within the browser.
The GraphQL feature is extending the standard [KGraphQL configuration](../Reference/configuration.md) and providing its own
set of configuration as described in the table below.

| Property | Description | Default value |
|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|
| endpoint | This specifies what route will be delivering the GraphQL endpoint. When `playground` is enabled, it will use this endpoint also. | `/graphql` |
| context | Refer to example below | |
| 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. | |
| 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. | |
| schema | This is where you are defining your schema. Please refer to [KGraphQL References](../Reference/operations.md) for further documentation on this. | ***required*** |
| Property | Description | Default value |
|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|
| endpoint | This specifies what route will be delivering the GraphQL endpoint. When `playground` is enabled, it will use this endpoint also. | `/graphql` |
| context | Allows to add call-specific information to the GraphQL context, see example below. | |
| 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. | |
| schema | This is where you are defining your schema. Please refer to [KGraphQL References](../Reference/operations.md) for further documentation on this. | ***required*** |

### Wrap

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

### Context

To get access to the context
`context` allows to add call-specific information to the GraphQL context:

=== "Example"
```kotlin
Expand All @@ -94,35 +93,6 @@ To get access to the context
}
```

### Error Handler

By default, KGraphQL will wrap non-`GraphQLError` exceptions into an `ExecutionException` (when `wrapErrors = true`)
or rethrow them to be handled by Ktor (when `wrapErrors = false`).

The `errorHandler` provides a way to **intercept and transform exceptions** before they are serialized.
It is always defined — by default it simply returns the same exception instance (`{ e -> e }`),
but you can override it to map specific exception types to `GraphQLError` or other `Throwable` instances.

=== "Example"
```kotlin
errorHandler { e ->
when (e) {
is ValidationException -> RequestError(e.message, extensions = mapOf("type" to "VALIDATION_ERROR"))
is DomainException -> RequestError(e.message, extensions = mapOf("type" to "DOMAIN_ERROR"))
is GraphQLError -> e
else -> ExecutionException(e.message ?: "Unknown execution error", cause = e)
}
}
schema {
query("hello") {
resolver { ctx: Context ->
val user = ctx.get<User>()!!
"Hello ${user.name}"
}
}
}
```

## Schema Definition Language (SDL)

The [Schema Definition Language](https://graphql.org/learn/schema/#type-language) (or Type System Definition Language) is a human-readable, language-agnostic
Expand Down
23 changes: 12 additions & 11 deletions docs/content/Reference/configuration.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
KGraphQL schema allows configuration of the following properties:

| Property | Description | Default value |
|--------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| useDefaultPrettyPrinter | Schema pretty prints JSON responses | `false` |
| useCachingDocumentParser | Schema caches parsed query documents | `true` |
| documentParserCacheMaximumSize | Schema document cache maximum size | `1000` |
| objectMapper | Schema is using Jackson ObjectMapper from this property | result of `jacksonObjectMapper()` from [jackson-kotlin-module](https://github.com/FasterXML/jackson-module-kotlin) |
| acceptSingleValueAsArray | Schema accepts single argument values as singleton list | `true` |
| 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) |
| 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) |
| wrapErrors | Schema wraps exceptions from resolvers as GraphQLError | |
| introspection | Schema allows introspection (also affects SDL). Introspection can be disabled in production to reduce attack surface. | `true` |
| Property | Description | Default value |
|--------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| useDefaultPrettyPrinter | Schema pretty prints JSON responses | `false` |
| useCachingDocumentParser | Schema caches parsed query documents | `true` |
| documentParserCacheMaximumSize | Schema document cache maximum size | `1000` |
| objectMapper | Schema is using Jackson ObjectMapper from this property | result of `jacksonObjectMapper()` from [jackson-kotlin-module](https://github.com/FasterXML/jackson-module-kotlin) |
| acceptSingleValueAsArray | Schema accepts single argument values as singleton list | `true` |
| 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) |
| 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) |
| wrapErrors | Schema wraps exceptions from resolvers as GraphQLError | |
| introspection | Schema allows introspection (also affects SDL). Introspection can be disabled in production to reduce attack surface. | `true` |
| 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/stuebingerb/kgraphql/schema/execution/ErrorHandler.kt) |

=== "Example"
```kotlin
Expand Down
39 changes: 38 additions & 1 deletion docs/content/Reference/errorHandling.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,41 @@ Those re-thrown exceptions could then be handled with the [`StatusPages` Ktor pl
Invalid input: java.lang.IllegalArgumentException: Illegal argument
```

Because exceptions are re-thrown, `wrapErrors = false` can never result in partial responses.
Because exceptions are re-thrown, `wrapErrors = false` can never result in partial responses. `wrapErrors = false` will
also not invoke a custom error handler. If you want to throw exceptions with custom mapping, use `wrapErrors = true` and
re-throw mapped exceptions from the error handler.

## Error Handler

In KGraphQL, the schema can [configure a custom _error handler_](configuration.md) that is called for each exception
encountered during execution. It can be used to customize default error mapping, and to add additional extensions to
the response.

The error handler is supposed to return a subclass of `GraphQLError`, which is either a `RequestError` or an `ExecutionError`
that will be handled according to the schema. When subclassing from the default `ErrorHandler`, mapping can be delegated
to the standard implementation, completely replaced, or a mixture of both.

=== "Example"
```kotlin
val customErrorHandler = object : ErrorHandler() {
override suspend fun handleException(ctx: Context, node: Execution.Node, exception: Throwable): GraphQLError {
return when (exception) {
is IllegalArgumentException -> ExecutionError(
message = exception.message ?: "",
node = node,
extensions = mapOf("type" to "CUSTOM_ERROR_TYPE")
)

is IllegalAccessException -> RequestError(
message = "You shall not pass!",
node = node.selectionNode,
extensions = mapOf("required_role" to "ADMIN", "reason" to "Gandalf")
)

else -> super.handleException(ctx, node, exception)
}
}
}
```

(!) Exceptions from the error handler itself are *not* wrapped, regardless of the `wrapErrors` configuration.
4 changes: 2 additions & 2 deletions kgraphql-ktor-stitched/api/kgraphql-ktor-stitched.api
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ public final class com/apurebase/kgraphql/stitched/StitchedKGraphQL {
}

public final class com/apurebase/kgraphql/stitched/schema/configuration/StitchedSchemaConfiguration : com/apurebase/kgraphql/configuration/SchemaConfiguration {
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
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
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
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
public final fun getLocalUrl ()Ljava/lang/String;
public final fun getRemoteExecutor ()Lcom/apurebase/kgraphql/stitched/schema/execution/RemoteRequestExecutor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.apurebase.kgraphql.stitched.schema.configuration
import com.apurebase.kgraphql.ExperimentalAPI
import com.apurebase.kgraphql.configuration.SchemaConfiguration
import com.apurebase.kgraphql.schema.execution.ArgumentTransformer
import com.apurebase.kgraphql.schema.execution.ErrorHandler
import com.apurebase.kgraphql.schema.execution.GenericTypeResolver
import com.apurebase.kgraphql.stitched.schema.execution.RemoteRequestExecutor
import com.fasterxml.jackson.databind.ObjectMapper
Expand All @@ -23,6 +24,7 @@ class StitchedSchemaConfiguration(
introspection: Boolean = true,
genericTypeResolver: GenericTypeResolver,
argumentTransformer: ArgumentTransformer,
errorHandler: ErrorHandler,
val remoteExecutor: RemoteRequestExecutor,
val localUrl: String?
) : SchemaConfiguration(
Expand All @@ -34,5 +36,6 @@ class StitchedSchemaConfiguration(
wrapErrors,
introspection,
genericTypeResolver,
argumentTransformer
argumentTransformer,
errorHandler
)
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ open class StitchedSchemaConfigurationDSL : SchemaConfigurationDSL() {
introspection = introspection,
genericTypeResolver = genericTypeResolver,
argumentTransformer = RemoteArgumentTransformer(objectMapper, genericTypeResolver),
errorHandler = errorHandler,
remoteExecutor = requireNotNull(remoteExecutor) { "Remote executor not defined" },
localUrl = localUrl
)
Expand Down
1 change: 0 additions & 1 deletion kgraphql-ktor/api/kgraphql-ktor.api
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ public final class com/apurebase/kgraphql/GraphQL {
public final class com/apurebase/kgraphql/GraphQL$Configuration : com/apurebase/kgraphql/schema/dsl/SchemaConfigurationDSL {
public fun <init> ()V
public final fun context (Lkotlin/jvm/functions/Function2;)V
public final fun errorHandler (Lkotlin/jvm/functions/Function1;)V
public final fun getEndpoint ()Ljava/lang/String;
public final fun getPlayground ()Z
public final fun schema (Lkotlin/jvm/functions/Function1;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,8 @@ class GraphQL(val schema: Schema) {
wrapWith = block
}

fun errorHandler(block: (e: Throwable) -> Throwable) {
errorHandler = block
}

internal var contextSetup: (ContextBuilder.(ApplicationCall) -> Unit)? = null
internal var wrapWith: (Route.(next: Route.() -> Unit) -> Unit)? = null
internal var errorHandler: ((Throwable) -> Throwable) = { e -> e }
internal var schemaBlock: (SchemaBuilder.() -> Unit)? = null
}

Expand Down Expand Up @@ -121,14 +116,9 @@ class GraphQL(val schema: Schema) {
coroutineScope {
proceed()
}
} catch (e: Throwable) {
val error = config.errorHandler(e)
if (error !is GraphQLError) {
throw e
}

} catch (e: GraphQLError) {
context.respondText(
error.serialize(),
e.serialize(),
ContentType.Application.Json,
HttpStatusCode.OK
)
Expand Down
Loading
Loading