Skip to content

[graphql-request] Include original response in ClientError #1473

@adambrgmn

Description

@adambrgmn

Perceived Problem

We have an api that implements certain stuff "outside" of graphql and might at any time return a non-graphql response back to our client, e.g. 401 Unauthorized or 403 Forbidden. In some of these responses we get a regular json body back with some details on why it went wrong.

While that body might not be useful for end users we do need access to it in our error handling and reporting to be able to more closely pin down what has happened.

The issue is that graphql-request will not provide any version of the response body if it couldn't parse the body as a graphql response.

Ideas / Proposed Solution(s)

My proposal is to include the original response in the ClientError. I have patched this locally (can be provided if interesting) and found it to be working fine.

Below is an extract of our response middleware, the response in this case holds some message, in json-format, as to why the kill switch was triggered.

if (res instanceof ClientError && res.originalResponse !== undefined) {
  // This could have been res.response.headers['Kill-Switch'], but headers is not typed properly
  //                                         ↓
  const hasKillSwitch = res.originalResponse.headers.get("Kill-Switch");
  if (hasKillSwitch) {
    const reason = await extractKillSwitchReason(res.originalResponse);
    throw new KillSwitchError(reason);
  }
}

The ClientError would look something like this:

export class ClientError extends Error {
  public response: GraphQLResponse
  public request: GraphQLRequestContext
+ public originalResponse?: Response

-  constructor(response: GraphQLResponse, request: GraphQLRequestContext) {
+  constructor(response: GraphQLResponse, request: GraphQLRequestContext, originalResponse?: Response) {
    const message = `${ClientError.extractMessage(response)}: ${
      JSON.stringify({
        response,
        request,
      })
    }`

    super(message)

    Object.setPrototypeOf(this, ClientError.prototype)

    this.response = response
    this.request = request
+   this.originalResponse = originalResponse

    // this is needed as Safari doesn't support .captureStackTrace
    if (typeof Error.captureStackTrace === `function`) {
      Error.captureStackTrace(this, ClientError)
    }
  }

  private static extractMessage(response: GraphQLResponse): string {
    return response.errors?.[0]?.message ?? `GraphQL Error (Code: ${String(response.status)})`
  }
}

I would be happy to align on this and provide a PR if this kind of feature would be interesting.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions