Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mixing of schema-level and app-level errors in GraphQL

While building a new application on top of a graphql API we have run into the following problem:

We have a mutation with an input field whose type is a custom scalar with its own validation rules (in this case that the input is a well-formed email address).

On the client, the user of the app fills in a bunch of fields and hits submit. Currently, validation of the email address is handled by the GraphQL layer and aborts the mutation if it fails with a top-level error. Validation of all other fields is handled by the mutation, returning app-level errors in the mutation payload. The other validations in this case cannot be represented directly in the schema since they involve inter-dependent fields.

This behaviour is really unhelpful for the client: it now has to know about errors in two possible locations (top-level graphql errors, and the application errors in the mutation payload) and in two possible formats. It also means that other malformed fields whose malformed-ness is not represented in the GraphQL schema will not be reported until all the schema-level issues have been fixed, forcing the user to go through multiple rounds of "fix the error, hit submit, get another error".

What is the standard solution to this problem? Putting validations (quite complex in this case) on the client? Weakening the schema in order to group all relevant validations at the application layer?

like image 407
Evan Avatar asked Jun 06 '16 19:06

Evan


People also ask

What is GraphQL error?

GraphQL errors These are errors related to the server-side execution of a GraphQL operation. They include: Syntax errors (e.g., a query was malformed) Validation errors (e.g., a query included a schema field that doesn't exist) Resolver errors (e.g., an error occurred while attempting to populate a query field)


1 Answers

The problem with error categorization

top-level graphql errors, and the application errors in the mutation payload

The distinction that you made between schema-level and application level errors is based on GraphQL type and mutation implementation. A client-side application usually expects a higher abstraction level of errors, i.e., it needs to distinguish user errors and system errors. That way it can mask the system errors as "internal error" and present the user errors as necessary. The developer also can inspect the set of system errors.

See a nice and concise article by Konstantin Tarkus on this: Validation and User Errors in GraphQL Mutations, whose approach I have followed in this answer.

A Not-so-standard-yet-makes-sense solution

To the best of my knowledge, there is no particular standard approach. However, you can try out the following approach.

First, having system-level errors in the top-level field errors of mutation response:

{
  "data": {
    "viewer": {
      "articles": {
        "edges": [
          {
            "node": {
              "title": "Sample article title",
              "tags": null
            }
          }
        ]
      }
    }
  },
  "errors": [
    {
      "message": "Cannot read property 'bar' of undefined",
      "locations": [
        {
          "line": 7,
          "column": 11
        }
      ]
    }
  ]
}

Second, putting user-level errors as a separate field errors in mutation payload. Example from the mentioned article:

{
  data: {
    user: null,
    errors: [
      '',
      'Failed to create a new user account.',
      'email',
      'User with this email address was already registered.',
    ]
  }
}
// The errors field is just an array of strings that follows this pattern —
// [argumentName1, errorMessage1, argumentName2, errorMessage2, … ]

The above approach enables the client to look for user errors in a defined format in a single place - the errors field of mutation payload. It also allows the client to receive all errors together.

This approach loses automatic validation of the mutation's input type. However, validation is not compromised as the input type's validation logic can be put in a separate function. This function will return validation errors as necessary, which will eventually be put in mutation payload's errors field.

By the way, thanks for the well-written question!

like image 77
Ahmad Ferdous Avatar answered Sep 22 '22 21:09

Ahmad Ferdous