From e2d654a680af29135db39473c45e47485be4468a Mon Sep 17 00:00:00 2001 From: Matthew Thorne Date: Wed, 6 May 2020 14:36:46 -0400 Subject: [PATCH 1/4] Add generic request to client. --- build.gradle.kts | 2 +- .../klient/graphql/GenericGraphQLResponse.kt | 9 ++ .../kotlin/klient/graphql/GraphQLClient.kt | 14 +++ .../graphql/internal/parsers/graphQLError.kt | 5 + .../internal/parsers/graphQLResponse.kt | 34 +++++++ src/test/kotlin/klient/graphql/ClientTest.kt | 92 +++++++++++++++++++ .../kotlin/klient/graphql/helpers/models.kt | 11 +++ .../kotlin/klient/graphql/helpers/server.kt | 21 +++++ 8 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/klient/graphql/GenericGraphQLResponse.kt diff --git a/build.gradle.kts b/build.gradle.kts index bf5e50d..3103a1b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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" project.group = "com.github.excitement-engineer" project.version = versionNo diff --git a/src/main/kotlin/klient/graphql/GenericGraphQLResponse.kt b/src/main/kotlin/klient/graphql/GenericGraphQLResponse.kt new file mode 100644 index 0000000..99f97f8 --- /dev/null +++ b/src/main/kotlin/klient/graphql/GenericGraphQLResponse.kt @@ -0,0 +1,9 @@ +package klient.graphql + +data class GenericGraphQLResponse ( + val data: Data? = null, + val errors: List = emptyList() +) { + val hasErrors = errors.isNotEmpty() + +} \ No newline at end of file diff --git a/src/main/kotlin/klient/graphql/GraphQLClient.kt b/src/main/kotlin/klient/graphql/GraphQLClient.kt index 498d052..b044d97 100644 --- a/src/main/kotlin/klient/graphql/GraphQLClient.kt +++ b/src/main/kotlin/klient/graphql/GraphQLClient.kt @@ -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 @@ -20,6 +21,19 @@ data class GraphQLClient( return parseResponse(httpResponse) } + + inline fun performGenericRequest(request: GraphQLRequest): GenericGraphQLResponse { + + val requestBody = request.serialize() + + val httpResponse = performHttpRequest( + url = endpoint, + headers = headers, + requestBody = requestBody + ) + + return parseGenericResponse(httpResponse) + } } diff --git a/src/main/kotlin/klient/graphql/internal/parsers/graphQLError.kt b/src/main/kotlin/klient/graphql/internal/parsers/graphQLError.kt index 487484e..c70e2fa 100644 --- a/src/main/kotlin/klient/graphql/internal/parsers/graphQLError.kt +++ b/src/main/kotlin/klient/graphql/internal/parsers/graphQLError.kt @@ -11,6 +11,11 @@ internal fun ArrayNode.parseErrors(): List = map { errorObject -> .parseError() } +internal fun ArrayNode.parseGenericErrors(errorClass: Class): List = map { errorObject -> + errorObject + .assertIsObject() + .parse(errorClass) +} private fun ObjectNode.parseError(): GraphQLError { diff --git a/src/main/kotlin/klient/graphql/internal/parsers/graphQLResponse.kt b/src/main/kotlin/klient/graphql/internal/parsers/graphQLResponse.kt index ca789bc..3d68ef0 100644 --- a/src/main/kotlin/klient/graphql/internal/parsers/graphQLResponse.kt +++ b/src/main/kotlin/klient/graphql/internal/parsers/graphQLResponse.kt @@ -1,5 +1,6 @@ package klient.graphql.internal.parsers +import klient.graphql.GenericGraphQLResponse import klient.graphql.internal.http.HTTPResponse import klient.graphql.internal.http.assertIsValid import klient.graphql.GraphQLResponse @@ -8,6 +9,12 @@ import klient.graphql.GraphQLResponse internal inline fun parseResponse(httpResponse: HTTPResponse) = parseResponse(httpResponse, Data::class.java) +@PublishedApi +internal inline fun parseGenericResponse(httpResponse: HTTPResponse) = + parseGenericResponse(httpResponse, Data::class.java, Error::class.java) + +//GenericGraphQLResponse + @PublishedApi internal fun parseResponse(httpResponse: HTTPResponse, responseClass: Class): GraphQLResponse { @@ -33,4 +40,31 @@ internal fun parseResponse(httpResponse: HTTPResponse, responseClass: Cla data = data, errors = errors ?: emptyList() ) +} + +@PublishedApi +internal fun parseGenericResponse(httpResponse: HTTPResponse, responseClass: Class, errorClass: Class): GenericGraphQLResponse { + + val validResponse = httpResponse.assertIsValid() + + val (response) = validResponse + + val jsonResponse = response + .toJson() + .assertIsObject() + + val data = jsonResponse + .safeGet("data") + ?.assertIsObject() + ?.parse(responseClass) + + val errors = jsonResponse + .safeGet("errors") + ?.assertIsList() + ?.parseGenericErrors(errorClass) + + return GenericGraphQLResponse( + data = data, + errors = errors ?: emptyList() + ) } \ No newline at end of file diff --git a/src/test/kotlin/klient/graphql/ClientTest.kt b/src/test/kotlin/klient/graphql/ClientTest.kt index d37e6f8..9713762 100644 --- a/src/test/kotlin/klient/graphql/ClientTest.kt +++ b/src/test/kotlin/klient/graphql/ClientTest.kt @@ -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( + GraphQLRequest( + query = """ + { + error + } + """.trimIndent() + ) + ) + + assertEquals( + expected = GenericGraphQLResponse( + data = ErrorResponse(null), + errors = listOf( + 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( + 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( diff --git a/src/test/kotlin/klient/graphql/helpers/models.kt b/src/test/kotlin/klient/graphql/helpers/models.kt index 77ee5b6..196ae34 100644 --- a/src/test/kotlin/klient/graphql/helpers/models.kt +++ b/src/test/kotlin/klient/graphql/helpers/models.kt @@ -1,5 +1,7 @@ package klient.graphql.helpers +import klient.graphql.Location + data class Person( val name: String, val age: Int, @@ -32,3 +34,12 @@ data class PersonWithoutSchool( val age: Int ) +data class CustomGraphQLError ( + val message: String, + val locations: List = emptyList(), + val path: List = emptyList(), + val extensions: Map = emptyMap(), + val test: String, + val customMessage: String +) + diff --git a/src/test/kotlin/klient/graphql/helpers/server.kt b/src/test/kotlin/klient/graphql/helpers/server.kt index dfec054..1d6c69f 100644 --- a/src/test/kotlin/klient/graphql/helpers/server.kt +++ b/src/test/kotlin/klient/graphql/helpers/server.kt @@ -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") From 4667b97348dad72d14b1172a22d612e228ae4a8f Mon Sep 17 00:00:00 2001 From: Matthew Thorne Date: Fri, 8 May 2020 15:11:51 -0400 Subject: [PATCH 2/4] Update version to three digits, refactor code to reduce duplication. --- build.gradle.kts | 2 +- .../internal/parsers/graphQLResponse.kt | 29 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3103a1b..817f08e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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.1" +val versionNo = "1.1.0" project.group = "com.github.excitement-engineer" project.version = versionNo diff --git a/src/main/kotlin/klient/graphql/internal/parsers/graphQLResponse.kt b/src/main/kotlin/klient/graphql/internal/parsers/graphQLResponse.kt index 3d68ef0..3d7c395 100644 --- a/src/main/kotlin/klient/graphql/internal/parsers/graphQLResponse.kt +++ b/src/main/kotlin/klient/graphql/internal/parsers/graphQLResponse.kt @@ -1,5 +1,6 @@ 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 @@ -13,18 +14,11 @@ internal inline fun parseResponse(httpResponse: HTTPResponse) = internal inline fun parseGenericResponse(httpResponse: HTTPResponse) = parseGenericResponse(httpResponse, Data::class.java, Error::class.java) -//GenericGraphQLResponse @PublishedApi internal fun parseResponse(httpResponse: HTTPResponse, responseClass: Class): GraphQLResponse { - val validResponse = httpResponse.assertIsValid() - - val (response) = validResponse - - val jsonResponse = response - .toJson() - .assertIsObject() + val jsonResponse = parseJsonResponse(httpResponse) val data = jsonResponse .safeGet("data") @@ -45,13 +39,7 @@ internal fun parseResponse(httpResponse: HTTPResponse, responseClass: Cla @PublishedApi internal fun parseGenericResponse(httpResponse: HTTPResponse, responseClass: Class, errorClass: Class): GenericGraphQLResponse { - val validResponse = httpResponse.assertIsValid() - - val (response) = validResponse - - val jsonResponse = response - .toJson() - .assertIsObject() + val jsonResponse = parseJsonResponse(httpResponse) val data = jsonResponse .safeGet("data") @@ -67,4 +55,15 @@ internal fun parseGenericResponse(httpResponse: HTTPResponse, resp 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 } \ No newline at end of file From daa7912244bac76b56d06e334170205709f106df Mon Sep 17 00:00:00 2001 From: Matthew Thorne Date: Fri, 8 May 2020 16:36:40 -0400 Subject: [PATCH 3/4] Update read me to show usage --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index 8aff983..01febc8 100644 --- a/README.md +++ b/README.md @@ -174,3 +174,39 @@ val response = client.performRequest( ) ``` + + +### 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 = emptyList(), + val path: List = emptyList(), + val extensions: Map = 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 = genericClient.performGenericRequest( GraphQLRequest(query = query)) + + +``` \ No newline at end of file From c821d3c52ee81c9d5d066de6361e1b1120845363 Mon Sep 17 00:00:00 2001 From: Matthew Thorne Date: Fri, 8 May 2020 16:44:57 -0400 Subject: [PATCH 4/4] Cleaning up read me. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 01febc8..2ba3120 100644 --- a/README.md +++ b/README.md @@ -206,7 +206,7 @@ val query = """ { } """.trimIndent() -val response = genericClient.performGenericRequest( GraphQLRequest(query = query)) +val response = client.performGenericRequest( GraphQLRequest(query = query)) ``` \ No newline at end of file