Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New client api performGenericRequest allows for custom graphql errors #5

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,39 @@ val response = client.performRequest<Response>(
)

```


### Using Generic Error
The `performGenericRequest` method allows developers to handle error responses from GraphQL servers that add additional information which does not exist in the GraphQL Specification.
```kt
data class CustomGraphQLError (
val message: String,
val locations: List<Location> = emptyList(),
val path: List<String> = emptyList(),
val extensions: Map<String, Any> = emptyMap(),
val test: String,
val customMessage: String
)

data class Response(
val person: Person
)

val client = GraphQLClient("http://example.com/graphql")

val query = """ {
person {
name
age
school {
name
address
}
}

} """.trimIndent()

val response = client.performGenericRequest<Response, CustomGraphQLError>( GraphQLRequest(query = query))


```
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import org.gradle.internal.impldep.org.joda.time.Instant
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

val kotlinVersion = "1.3.21"
val versionNo = "1.0.1"
val versionNo = "1.1.0"

project.group = "com.github.excitement-engineer"
project.version = versionNo
Expand Down
9 changes: 9 additions & 0 deletions src/main/kotlin/klient/graphql/GenericGraphQLResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package klient.graphql

data class GenericGraphQLResponse<Data, Error> (
val data: Data? = null,
val errors: List<Error> = emptyList()
) {
val hasErrors = errors.isNotEmpty()

}
14 changes: 14 additions & 0 deletions src/main/kotlin/klient/graphql/GraphQLClient.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package klient.graphql

import klient.graphql.internal.http.performHttpRequest
import klient.graphql.internal.parsers.parseGenericResponse
import klient.graphql.internal.parsers.parseResponse
import klient.graphql.internal.serializers.serialize

Expand All @@ -20,6 +21,19 @@ data class GraphQLClient(

return parseResponse(httpResponse)
}

inline fun <reified Data, reified Error> performGenericRequest(request: GraphQLRequest): GenericGraphQLResponse<Data, Error> {

val requestBody = request.serialize()

val httpResponse = performHttpRequest(
url = endpoint,
headers = headers,
requestBody = requestBody
)

return parseGenericResponse(httpResponse)
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ internal fun ArrayNode.parseErrors(): List<GraphQLError> = map { errorObject ->
.parseError()
}

internal fun <T>ArrayNode.parseGenericErrors(errorClass: Class<T>): List<T> = map { errorObject ->
errorObject
.assertIsObject()
.parse(errorClass)
}

private fun ObjectNode.parseError(): GraphQLError {

Expand Down
45 changes: 39 additions & 6 deletions src/main/kotlin/klient/graphql/internal/parsers/graphQLResponse.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package klient.graphql.internal.parsers

import com.fasterxml.jackson.databind.node.ObjectNode
import klient.graphql.GenericGraphQLResponse
import klient.graphql.internal.http.HTTPResponse
import klient.graphql.internal.http.assertIsValid
import klient.graphql.GraphQLResponse
Expand All @@ -9,15 +11,14 @@ internal inline fun <reified Data> parseResponse(httpResponse: HTTPResponse) =
parseResponse(httpResponse, Data::class.java)

@PublishedApi
internal fun <Data> parseResponse(httpResponse: HTTPResponse, responseClass: Class<Data>): GraphQLResponse<Data> {
internal inline fun <reified Data, reified Error> parseGenericResponse(httpResponse: HTTPResponse) =
parseGenericResponse(httpResponse, Data::class.java, Error::class.java)

val validResponse = httpResponse.assertIsValid()

val (response) = validResponse
@PublishedApi
internal fun <Data> parseResponse(httpResponse: HTTPResponse, responseClass: Class<Data>): GraphQLResponse<Data> {

val jsonResponse = response
.toJson()
.assertIsObject()
val jsonResponse = parseJsonResponse(httpResponse)

val data = jsonResponse
.safeGet("data")
Expand All @@ -33,4 +34,36 @@ internal fun <Data> parseResponse(httpResponse: HTTPResponse, responseClass: Cla
data = data,
errors = errors ?: emptyList()
)
}

@PublishedApi
internal fun <Data, Error> parseGenericResponse(httpResponse: HTTPResponse, responseClass: Class<Data>, errorClass: Class<Error>): GenericGraphQLResponse<Data, Error> {

val jsonResponse = parseJsonResponse(httpResponse)

val data = jsonResponse
.safeGet("data")
?.assertIsObject()
?.parse(responseClass)

val errors = jsonResponse
.safeGet("errors")
?.assertIsList()
?.parseGenericErrors(errorClass)

return GenericGraphQLResponse(
data = data,
errors = errors ?: emptyList()
)
}

private fun parseJsonResponse(httpResponse: HTTPResponse): ObjectNode {
val validResponse = httpResponse.assertIsValid()

val (response) = validResponse

val jsonResponse = response
.toJson()
.assertIsObject()
return jsonResponse
}
92 changes: 92 additions & 0 deletions src/test/kotlin/klient/graphql/ClientTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,98 @@ class ClientTest {
)
)

private val genericClient = GraphQLClient(
endpoint = "http://localhost:$serverPort/graphql-generic",
headers = mapOf(
"Authorization" to "abc123"
)
)

@Test
fun `generic parses an error`() {

val response = genericClient.performGenericRequest<ErrorResponse, CustomGraphQLError>(
GraphQLRequest(
query = """
{
error
}
""".trimIndent()
)
)

assertEquals(
expected = GenericGraphQLResponse(
data = ErrorResponse(null),
errors = listOf<CustomGraphQLError>(
CustomGraphQLError(
message = "Exception while fetching data (/error) : Something went wrong",
locations = listOf(
Location(
line = 2,
column = 5
)
),
path = listOf("error"),
extensions = mapOf(
"reason" to "unknown"
),
test = "test",
customMessage = "customMessage"
)
)
),
actual = response
)


assertTrue {
response.hasErrors
}
}

@Test
fun ` generic errors query returns successful for performGenericRequest`() {

val response =
genericClient.performGenericRequest<Response, CustomGraphQLError>(
GraphQLRequest(
query = """ {
person {
name
age
school {
name
address
}
}

} """.trimIndent()
)
)

assertEquals(
expected = GenericGraphQLResponse(
data = Response(
person = Person(
name = "John",
age = 18,
school = School(
name = "elementary",
address = "Main street"
)
)
)
),
actual = response
)

assertFalse {
response.hasErrors
}

}

@Test
fun `returns the expected response`() {
val response = client.performRequest<Response>(
Expand Down
11 changes: 11 additions & 0 deletions src/test/kotlin/klient/graphql/helpers/models.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package klient.graphql.helpers

import klient.graphql.Location

data class Person(
val name: String,
val age: Int,
Expand Down Expand Up @@ -32,3 +34,12 @@ data class PersonWithoutSchool(
val age: Int
)

data class CustomGraphQLError (
val message: String,
val locations: List<Location> = emptyList(),
val path: List<String> = emptyList(),
val extensions: Map<String, Any> = emptyMap(),
val test: String,
val customMessage: String
)

21 changes: 21 additions & 0 deletions src/test/kotlin/klient/graphql/helpers/server.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,27 @@ val server: ApplicationEngine = embeddedServer(Jetty, serverPort) {
}
}

graphQL("/graphql-generic", schema) {
config {
context = Context(
accessToken = call.request.header("Authorization")
)

formatError = {

val error = toSpecification()

error["extensions"] = mapOf(
"reason" to "unknown"
)
error["customMessage"] = "customMessage"
error["test"] = "test"

error
}
}
}

route("error") {
post {
call.respond(HttpStatusCode.InternalServerError, "Something went wrong")
Expand Down