Skip to content

Commit

Permalink
(Partly) take over aPureBase/KGraphQL#171
Browse files Browse the repository at this point in the history
  • Loading branch information
stuebingerb committed Oct 9, 2024
1 parent a3629cb commit bc01867
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -109,27 +109,5 @@ class GraphQL(val schema: Schema) {
}
return GraphQL(schema)
}

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()

}


}
53 changes: 52 additions & 1 deletion kgraphql/src/main/kotlin/com/apurebase/kgraphql/GraphQLError.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.apurebase.kgraphql

import com.apurebase.kgraphql.helpers.toJsonElement
import com.apurebase.kgraphql.schema.model.ast.ASTNode
import com.apurebase.kgraphql.schema.model.ast.Location.Companion.getLocation
import com.apurebase.kgraphql.schema.model.ast.Source
import kotlinx.serialization.json.addJsonObject
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

open class GraphQLError(

Expand Down Expand Up @@ -33,11 +38,28 @@ open class GraphQLError(
/**
* The original error thrown from a field resolver during execution.
*/
val originalError: Throwable? = null
val originalError: Throwable? = null,
val extensionsErrorType: String? = "INTERNAL_SERVER_ERROR",
val extensionsErrorDetail: Map<String, Any?>? = null
) : Exception(message) {

constructor(message: String, node: ASTNode?) : this(message, nodes = node?.let(::listOf))

constructor(message: String, extensionsErrorType: String?) : this(
message,
null,
null,
null,
null,
extensionsErrorType
)

constructor(
message: String,
extensionsErrorType: String?,
extensionsErrorDetail: Map<String, Any?>?
) : this(message, null, null, null, null, extensionsErrorType, extensionsErrorDetail)

/**
* An array of { line, column } locations within the source GraphQL document
* that correspond to this error.
Expand Down Expand Up @@ -71,4 +93,33 @@ open class GraphQLError(

return output
}

open val extensions: Map<String, Any?>? by lazy {
val extensions = mutableMapOf<String, Any?>()
extensionsErrorType?.let { extensions.put("type", extensionsErrorType) }
extensionsErrorDetail?.let { extensions.put("detail", extensionsErrorDetail) }
extensions
}

open fun serialize(): String = buildJsonObject {
put("errors", buildJsonArray {
addJsonObject {
put("message", message)
put("locations", buildJsonArray {
locations?.forEach {
addJsonObject {
put("liane", it.line)
put("column", it.column)
}
}
})
put("path", buildJsonArray {
// TODO: Build this path. https://spec.graphql.org/June2018/#example-90475
})
extensions?.let {
put("extensions", it.toJsonElement())
}
}
})
}.toString()
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,65 @@
package com.apurebase.kgraphql.helpers

import com.apurebase.kgraphql.schema.execution.Execution
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive

/**
* This returns a list of all scalar fields requested on this type.
*/
fun Execution.getFields(): List<String> = when (this) {
is Execution.Fragment -> elements.flatMap(Execution::getFields)
is Execution.Node -> {
if (children.isEmpty()) listOf(key)
else children
.filterNot { (it is Execution.Node && it.children.isNotEmpty()) }
.flatMap(Execution::getFields)

if (children.isEmpty()) {
listOf(key)
} else {
children
.filterNot { (it is Execution.Node && it.children.isNotEmpty()) }
.flatMap(Execution::getFields)
}
}
}.distinct()


/**
* Collection : Convert to JsonElement
*/
fun Collection<*>.toJsonElement(): JsonElement {
val list: MutableList<JsonElement> = mutableListOf()
forEach {
val value = it ?: return@forEach
when (value) {
is Number -> list.add(JsonPrimitive(value))
is Boolean -> list.add(JsonPrimitive(value))
is String -> list.add(JsonPrimitive(value))
is Map<*, *> -> list.add((value).toJsonElement())
is Collection<*> -> list.add(value.toJsonElement())
is Array<*> -> list.add(value.toList().toJsonElement())
else -> list.add(JsonPrimitive(value.toString())) // other type
}
}
return JsonArray(list)
}

/**
* Map : Convert to JsonElement
*/
fun Map<*, *>.toJsonElement(): JsonElement {
val map: MutableMap<String, JsonElement> = mutableMapOf()
forEach {
val key = it.key as? String ?: return@forEach
val value = it.value ?: return@forEach
when (value) {
is Number -> map[key] = JsonPrimitive(value)
is Boolean -> map[key] = JsonPrimitive(value)
is String -> map[key] = JsonPrimitive(value)
is Map<*, *> -> map[key] = (value).toJsonElement()
is Collection<*> -> map[key] = value.toJsonElement()
is Array<*> -> map[key] = value.toList().toJsonElement()
else -> map[key] = JsonPrimitive(value.toString()) // other type
}
}
return JsonObject(map)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.apurebase.kgraphql

import kotlinx.serialization.json.addJsonObject
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import org.amshove.kluent.shouldBeEqualTo
import org.junit.jupiter.api.Test

class GraphQLErrorTest {

@Test
fun `test graphql error with custom error type`() {
val graphqlError = GraphQLError(
message = "test",
extensionsErrorType = "AUTHORIZATION_ERROR"
)

val expectedJson = buildJsonObject {
put("errors", buildJsonArray {
addJsonObject {
put("message", "test")
put("locations", buildJsonArray {})
put("path", buildJsonArray {})
put("extensions", buildJsonObject {
put("type", "AUTHORIZATION_ERROR")
})
}
})
}.toString()

graphqlError.serialize() shouldBeEqualTo expectedJson
}

@Test
fun `test graphql error with custom error type and detail`() {
val graphqlError = GraphQLError(
message = "test",
extensionsErrorType = "VALIDATION_ERROR",
extensionsErrorDetail = mapOf<String, Any?>(
"singleCheck" to mapOf("email" to "not an email", "age" to "Limited to 150"),
"multiCheck" to "The 'from' number must not exceed the 'to' number"
)
)

val expectedJson = buildJsonObject {
put("errors", buildJsonArray {
addJsonObject {
put("message", "test")
put("locations", buildJsonArray {})
put("path", buildJsonArray {})
put("extensions", buildJsonObject {
put("type", "VALIDATION_ERROR")
put("detail", buildJsonObject {
put("singleCheck", buildJsonObject {
put("email", "not an email")
put("age", "Limited to 150")
})
put("multiCheck", "The 'from' number must not exceed the 'to' number")
})
})
}
})
}.toString()

graphqlError.serialize() shouldBeEqualTo expectedJson
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package com.apurebase.kgraphql.integration

import com.apurebase.kgraphql.*
import com.apurebase.kgraphql.GraphQLError
import com.apurebase.kgraphql.ValidationException
import com.apurebase.kgraphql.assertNoErrors
import com.apurebase.kgraphql.defaultSchema
import com.apurebase.kgraphql.deserialize
import com.apurebase.kgraphql.extract
import com.apurebase.kgraphql.helpers.getFields
import com.apurebase.kgraphql.schema.execution.Execution
import org.amshove.kluent.*
import org.amshove.kluent.invoking
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldThrow
import org.amshove.kluent.with
import org.amshove.kluent.withMessage
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.jupiter.api.Test
Expand Down Expand Up @@ -110,6 +118,16 @@ class QueryTest : BaseSchemaTest() {
} shouldThrow GraphQLError::class withMessage "Property author on Scenario does not exist"
}

@Test
fun `test default error extension`() {
invoking {
execute("{scenario{author, content}}")
} shouldThrow GraphQLError::class with {
extensionsErrorType shouldBeEqualTo "INTERNAL_SERVER_ERROR"
extensionsErrorDetail shouldBeEqualTo null
}
}

@Test
fun `query with interface`() {
val map = execute("{randomPerson{name \n age}}")
Expand Down Expand Up @@ -381,5 +399,4 @@ class QueryTest : BaseSchemaTest() {
result.extract<String>("data/root/kids[0]/name") shouldBeEqualTo "kids-Testing"
result.extract<List<String>>("data/root/kids[0]/fields") shouldBeEqualTo listOf("id", "fields", "name")
}

}

0 comments on commit bc01867

Please sign in to comment.