Repository: smallrye/smallrye-graphql
Issue: Handle errors with a `null` error `message`

The typesafe client throws a `java.lang.ClassCastException: class javax.json.JsonValueImpl cannot be cast to class javax.json.JsonString` if the response is something like:

```json
{
  "errors": [
    {
      "message": null,
      "locations": [
   ...
```

Reason: the `ResultBuilder` simply returns `jsonObject.getString("message");` which fails on a `JsonValue.NULL`.

Changed files:
client/tck/src/main/java/tck/graphql/typesafe/ErrorBehavior.java
client/api/src/main/java/io/smallrye/graphql/client/typesafe/api/GraphQLClientError.java
client/implementation/src/main/java/io/smallrye/graphql/client/typesafe/impl/ResultBuilder.java

diff --git a/client/api/src/main/java/io/smallrye/graphql/client/typesafe/api/GraphQLClientError.java b/client/api/src/main/java/io/smallrye/graphql/client/typesafe/api/GraphQLClientError.java
index 953adec1..06c00e48 100644
--- a/client/api/src/main/java/io/smallrye/graphql/client/typesafe/api/GraphQLClientError.java
+++ b/client/api/src/main/java/io/smallrye/graphql/client/typesafe/api/GraphQLClientError.java
@@ -27,12 +27,13 @@ public interface GraphQLClientError {
     default String defaultToString() {
         String errorCode = getErrorCode();
         List<Object> path = getPath();
+        String message = getMessage();
         List<SourceLocation> locations = getLocations();
         Map<String, Object> extensions = getExtensions();
-        return ((errorCode == null) ? "" : errorCode + ": ")
+        return (((errorCode == null) ? "" : errorCode + ": ")
                 + ((path == null) ? "" : path + " ")
-                + getMessage()
-                + ((locations == null) ? "" : " " + locations)
-                + ((extensions == null || extensions.isEmpty()) ? "" : " " + extensions);
+                + ((message == null) ? "" : message + " ")
+                + ((locations == null) ? "" : locations + " ")
+                + ((extensions == null || extensions.isEmpty()) ? "" : extensions + " ")).trim();
     }
 }
diff --git a/client/implementation/src/main/java/io/smallrye/graphql/client/typesafe/impl/ResultBuilder.java b/client/implementation/src/main/java/io/smallrye/graphql/client/typesafe/impl/ResultBuilder.java
index 8235ee8f..d3a70f41 100644
--- a/client/implementation/src/main/java/io/smallrye/graphql/client/typesafe/impl/ResultBuilder.java
+++ b/client/implementation/src/main/java/io/smallrye/graphql/client/typesafe/impl/ResultBuilder.java
@@ -85,7 +85,7 @@ public class ResultBuilder {
         return new GraphQLClientError() {
             @Override
             public String getMessage() {
-                return jsonObject.getString("message");
+                return jsonObject.getString("message", null);
             }
 
             @Override
diff --git a/client/tck/src/main/java/tck/graphql/typesafe/ErrorBehavior.java b/client/tck/src/main/java/tck/graphql/typesafe/ErrorBehavior.java
index df33d2a2..1660660a 100644
--- a/client/tck/src/main/java/tck/graphql/typesafe/ErrorBehavior.java
+++ b/client/tck/src/main/java/tck/graphql/typesafe/ErrorBehavior.java
@@ -107,6 +107,68 @@ class ErrorBehavior {
         then(error.getExtensions().get("classification")).isEqualTo("ValidationError");
     }
 
+    @Test
+    void shouldFailOnErrorWithoutMessage() {
+        fixture.returns("{" +
+                "\\"data\\":{\\"greeting\\":null}," +
+                "\\"errors\\":[{" +
+                /**/"\\"locations\\":[{\\"line\\":1,\\"column\\":2,\\"sourceName\\":\\"loc\\"}]," +
+                /**/"\\"path\\": [\\"greeting\\"],\\n" +
+                /**/"\\"extensions\\":{" +
+                /**//**/"\\"description\\":\\"some description\\"," +
+                /**//**/"\\"queryPath\\":[\\"greeting\\"]," +
+                /**//**/"\\"classification\\":\\"DataFetchingException\\"," +
+                /**//**/"\\"code\\":\\"no-greeting\\"}" +
+                "}]}}");
+        StringApi api = fixture.build(StringApi.class);
+
+        GraphQLClientException thrown = catchThrowableOfType(api::greeting, GraphQLClientException.class);
+
+        then(thrown).hasMessage("errors from service (and we can't apply them to a java.lang.String value for" +
+                " tck.graphql.typesafe.ErrorBehavior$StringApi#greeting; see ErrorOr)");
+        then(thrown).hasToString(
+                "GraphQlClientException: errors from service (and we can't apply them to a java.lang.String value for "
+                        + StringApi.class.getName() + "#greeting; see ErrorOr)\\n" +
+                        "errors:\\n" +
+                        "- no-greeting: [greeting] [(1:2@loc)]" +
+                        " {description=some description, queryPath=[greeting], classification=DataFetchingException, code=no-greeting})");
+        then(thrown.getErrors()).hasSize(1);
+        GraphQLClientError error = thrown.getErrors().get(0);
+        then(error.getMessage()).isNull();
+        then(error.getLocations()).containsExactly(new SourceLocation(1, 2, "loc"));
+        then(error.getPath()).containsExactly("greeting");
+        then(error.getErrorCode()).isEqualTo("no-greeting");
+    }
+
+    @Test
+    void shouldFailOnErrorWithNullMessage() {
+        fixture.returns("{\\n" +
+                "  \\"errors\\": [\\n" +
+                "    {\\n" +
+                "      \\"message\\": null,\\n" +
+                "      \\"extensions\\": {\\n" +
+                "        \\"classification\\": \\"SomeClassification\\"\\n" +
+                "      }\\n" +
+                "    }\\n" +
+                "  ],\\n" +
+                "  \\"data\\": null\\n" +
+                "}\\n");
+        StringApi api = fixture.build(StringApi.class);
+
+        GraphQLClientException thrown = catchThrowableOfType(api::greeting, GraphQLClientException.class);
+
+        then(thrown).hasMessage("errors from service");
+        then(thrown).hasToString("GraphQlClientException: errors from service\\n" +
+                "errors:\\n" +
+                "- {classification=SomeClassification})");
+        then(thrown.getErrors()).hasSize(1);
+        GraphQLClientError error = thrown.getErrors().get(0);
+        then(error.getMessage()).isNull();
+        then(error.getLocations()).isNull();
+        then(error.getErrorCode()).isNull();
+        then(error.getExtensions().get("classification")).isEqualTo("SomeClassification");
+    }
+
     @Test
     void shouldFailOnErrorWithoutExtensions() {
         fixture.returns("{\\n" +