Error Handling with Connectors

Mapping responses for non-200 HTTP responses


Learn how Connectors handle errors by default and how you can customize error handling.

Default behavior

When an HTTP endpoint returns a non-200 status, GraphOS Router includes errors in the GraphQL errors array of the response. It uses the following default error structure:

JSON
{
  "data": null,
  "errors": [
    {
      "message": "Request failed",
      "path": ["products"],
      "extensions": {
        "http": {
          "status": 500
        },
        "connector": {
          "coordinate": "ecomm:Query.products@connect[0]"
        },
        "code": "CONNECTOR_FETCH",
        "service": "ecomm"
      }
    }
  ]
}

Fields that return non-200 HTTP responses are set to null in the response's data attribute, following the GraphQL specification.

note
To include all the fields in the example error response, ensure you configure subgraph error inclusion in the router.

Error handling customization

You can customize errors in the response using the errors property on @source or @connect directives. The errors property lets you customize the message and extensions fields in errors objects in the GraphQL response using information from the HTTP response and literal values.

JSON
{
  "data": null,
  "errors": [
    {
      "message": "Custom message",
      "path": ["products"],
      "extensions": {
        "http": {
          "customField": "Custom value"
        },
      /// ...
      }
    }
  ]
}

The errors.message and errors.extensions fields take mapping expressions, which are applied on the response body, just like selection mapping. You can access all of the mapping language's variables and methods in these fields.

Customizing errors.message

You can set a custom error message using the errors.message property on @connect and @source directives. For example, this schema:

GraphQL
type Query {
  users: [User]
    @connect(
      http: { GET: "http://my-api.com/users" }
      selection: "id name"
      errors: {
        message: "error.message"
      }
    )
}

results in a GraphQL response with a customized errors.message:

JSON
{
  "data": null,
  "errors": [
    {
      "message": "There was a critical failure!",
      "path": ["users"],
      /// ...
    }
  ]
}

The errors.message property expects a mapping expression that results in a string.

Valid error.message mapping expressions

The following are examples of valid mapping expressions for errors.message:

  • Using a static literal value:

    GraphQL
    @connect(
      # --snip --
      errors: {
        message: "$('This is a hard-coded literal message')"
      }
    )
  • Using a response field (in particular, error.message)`:

    GraphQL
    @connect(
      # --snip --
      errors: {
        message: "error.message"
      }
    )
  • Using a header value from the response with the $response.headers variable:

    GraphQL
    @connect(
      # --snip --
      errors: {
        message: "$response.headers.errorheader->first"
      }
    )

Invalid error.message mapping expressions

The following are examples of invalid mapping expressions for errors.message:

  • Using a value that doesn't result in a string, for example, using the $status variable, which returns a number:

    GraphQL
    ❌ Invalid example
    @connect(
      # --snip --
      errors: {
        message: "$status"
      }
    )
  • Using an object format:

    GraphQL
    ❌ Invalid example
    @connect(
      # --snip --
      errors: {
        message: "myProperty: error.message"
      }
    )

error.message precedence rules

If both @source and @connect set errors.message, the @connect's value takes precedence.

Customizing errors.extensions

You can set custom error extensions using the errors.extensions property on @connect and @source directives.

GraphQL
type Query {
  users: [User]
    @connect(
      http: { GET: "http://my-api.com/users" }
      selection: "id name"
      errors: {
        extensions: """
          code: error.code
          status: $status
        """
      }
    )
}

This customization sets additional properties in the GraphQL errors.extensions object:

JSON
{
  "data": null,
  "errors": [
    {
      "message": "Request failed",
      "path": ["users"],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "status": 500,
        "http": {
          "status": 500
        },
        "connector": {
          "coordinate": "connector-graph:Query.users@connect[0]"
        }
      }
    }
  ]
}

The errors.extensions property expects a mapping expression that results in an object.

Valid errors.extensions mapping expressions

The following are examples of valid mapping expressions for errors.extensions:

  • Using properties from the response:

    GraphQL
    @connect(
      # --snip --
      errors: {
        extensions: """
          code: error.code
        """
      }
    )
  • Using the $status variable:

    GraphQL
    @connect(
      # --snip --
      errors: {
        extensions: """
          status: $status
        """
      }
    )
  • Using literal values in a nested structure:

    GraphQL
    @connect(
      # --snip --
      errors: {
        extensions: """
          http: {
            myField: $("literal Value")
          }
        """
      }
    )
  • Combining multiple properties from different sources:

    GraphQL
    @connect(
      # --snip --
      errors: {
        extensions: """
          code: error.code
          statusCode: $status
          errorDetails: {
            message: error.message
            timestamp: $response.headers."x-error-timestamp"->first
            source: $("backend-service")
          }
        """
      }
    )

Invalid errors.extensions mapping expressions

The following example is invalid because it uses a value that doesn't result in an object:

GraphQL
❌ Invalid example
@connect(
  # --snip --
  errors: {
    extensions: "$status"
  }
)

errors.extensions precedence rules

If both @source and the @connect set errors.extensions properties, the response contains both. The extensions object also retains its default properties. When properties are found in more than one of the three:

  • Properties set in @source overwrite default properties

  • Properties set in @connect overwrite @source properties

Additional resources

Feedback

Ask Community