Skip to content

Commit

Permalink
#37: Better error support for execution errors
Browse files Browse the repository at this point in the history
  • Loading branch information
jeggy committed Feb 7, 2021
1 parent 17cabcc commit 6670f0b
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package com.apurebase.kgraphql
import com.apurebase.kgraphql.schema.dsl.SchemaBuilder
import com.apurebase.kgraphql.schema.dsl.SchemaConfigurationDSL
import io.ktor.application.*
import io.ktor.http.ContentType
import io.ktor.request.receiveText
import io.ktor.response.respond
import io.ktor.response.respondBytes
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.util.AttributeKey
import io.ktor.util.*
import kotlinx.coroutines.coroutineScope
import kotlinx.serialization.json.*
import kotlinx.serialization.json.Json.Default.decodeFromString

class GraphQL {
Expand Down Expand Up @@ -75,7 +76,38 @@ class GraphQL {

pipeline.featureOrNull(Routing)?.apply(routing) ?: pipeline.install(Routing, routing)

pipeline.intercept(ApplicationCallPipeline.Monitoring) {
try {
coroutineScope {
proceed()
}
} catch (e: Throwable) {
if (e is GraphQLError) {
context.respond(HttpStatusCode.OK, e.serialize())
} else throw e
}
}
return GraphQL()
}

private fun GraphQLError.serialize(): String = buildJsonObject {
put("errors", buildJsonArray {
addJsonObject {
put("message", message)
put("locations", buildJsonArray {
locations?.forEach {
addJsonObject {
put("line", it.line)
put("column", it.column)
}
}
})
put("path", buildJsonArray {
// TODO: Build this path. https://spec.graphql.org/June2018/#example-90475
})
}
})
}.toString()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ data class SchemaConfiguration(
//execution
val coroutineDispatcher: CoroutineDispatcher,

val wrapErrors: Boolean,

val executor: Executor,
val timeout: Long?,
val plugins: MutableMap<KClass<*>, Any>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.apurebase.kgraphql.schema

import com.apurebase.kgraphql.Context
import com.apurebase.kgraphql.GraphQLError
import com.apurebase.kgraphql.configuration.SchemaConfiguration
import com.apurebase.kgraphql.request.CachingDocumentParser
import com.apurebase.kgraphql.request.VariablesJson
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ open class SchemaConfigurationDSL {
var documentParserCacheMaximumSize: Long = 1000L
var acceptSingleValueAsArray: Boolean = true
var coroutineDispatcher: CoroutineDispatcher = Dispatchers.Default
var wrapErrors: Boolean = true
var executor: Executor = Executor.Parallel
var timeout: Long? = null

Expand All @@ -38,6 +39,7 @@ open class SchemaConfigurationDSL {
objectMapper,
useDefaultPrettyPrinter,
coroutineDispatcher,
wrapErrors,
executor,
timeout,
plugins
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.apurebase.kgraphql.schema.execution

import com.apurebase.kgraphql.Context
import com.apurebase.kgraphql.ExecutionException
import com.apurebase.kgraphql.GraphQLError
import com.apurebase.kgraphql.request.Variables
import com.apurebase.kgraphql.request.VariablesJson
import com.apurebase.kgraphql.schema.DefaultSchema
Expand Down Expand Up @@ -412,9 +413,13 @@ class DataLoaderPreparedRequestExecutor(val schema: DefaultSchema) : RequestExec
ctx.requestContext
)

return when {
hasReceiver -> invoke(receiver, *transformedArgs.toTypedArray())
else -> invoke(*transformedArgs.toTypedArray())
return try {
if (hasReceiver) invoke(receiver, *transformedArgs.toTypedArray())
else invoke(*transformedArgs.toTypedArray())
} catch (e: Throwable) {
if (schema.configuration.wrapErrors && e !is GraphQLError) {
throw GraphQLError(e.message ?: "", nodes = listOf(executionNode.selectionNode), originalError = e)
} else throw e
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.apurebase.kgraphql.schema.execution

import com.apurebase.kgraphql.Context
import com.apurebase.kgraphql.ExecutionException
import com.apurebase.kgraphql.*
import com.apurebase.kgraphql.request.Variables
import com.apurebase.kgraphql.request.VariablesJson
import com.apurebase.kgraphql.schema.DefaultSchema
Expand Down Expand Up @@ -329,13 +328,19 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
): T? {
val transformedArgs = argumentsHandler.transformArguments(funName, inputValues, args, ctx.variables, executionNode, ctx.requestContext)
//exceptions are not caught on purpose to pass up business logic errors
return when {
hasReceiver -> invoke(receiver, *transformedArgs.toTypedArray())
isSubscription -> {
val subscriptionArgs = children.map { (it as Execution.Node).aliasOrKey }
invoke(transformedArgs, subscriptionArgs, objectWriter)
return try {
when {
hasReceiver -> invoke(receiver, *transformedArgs.toTypedArray())
isSubscription -> {
val subscriptionArgs = children.map { (it as Execution.Node).aliasOrKey }
invoke(transformedArgs, subscriptionArgs, objectWriter)
}
else -> invoke(*transformedArgs.toTypedArray())
}
else -> invoke(*transformedArgs.toTypedArray())
} catch (e: Throwable) {
if (schema.configuration.wrapErrors && e !is GraphQLError ) {
throw GraphQLError(e.message ?: "", nodes = listOf(executionNode.selectionNode), originalError = e)
} else throw e
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
package com.apurebase.kgraphql.specification.language

import com.apurebase.kgraphql.Specification
import com.apurebase.kgraphql.defaultSchema
import com.apurebase.kgraphql.executeEqualQueries
import com.apurebase.kgraphql.expect
import com.apurebase.kgraphql.*
import com.apurebase.kgraphql.schema.DefaultSchema
import com.apurebase.kgraphql.schema.SchemaException
import com.apurebase.kgraphql.schema.dsl.operations.subscribe
import com.apurebase.kgraphql.schema.dsl.operations.unsubscribe
import com.apurebase.kgraphql.schema.execution.Executor
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldEqual
import org.amshove.kluent.*
import org.junit.jupiter.api.Test

data class Actor(var name : String? = "", var age: Int? = 0)
Expand Down Expand Up @@ -105,8 +101,11 @@ class OperationsSpecificationTest {

@Test
fun `Subscription return type must be the same as the publisher's`(){
expect<SchemaException>("Subscription return type must be the same as the publisher's"){
invoking {
newSchema(Executor.Parallel).executeBlocking("subscription {subscriptionActress(subscription : \"mySubscription\"){age}}")
} shouldThrow GraphQLError::class with {
originalError shouldBeInstanceOf SchemaException::class
message shouldBeEqualTo "Subscription return type must be the same as the publisher's"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ class NonNullSpecificationTest {
resolver { string : String? -> string!! }
}
}
expect<NullPointerException> {
invoking {
schema.executeBlocking("{nonNull}")
} shouldThrow GraphQLError::class with {
originalError shouldBeInstanceOf java.lang.NullPointerException::class
}
}

Expand Down

0 comments on commit 6670f0b

Please sign in to comment.