Error Handling

#HttpProblems Helper

Zuplo encourages developers to build APIs with standard and actionable error messages. While any error format is easily supported, we default and encorage developers to adopt the Problem Details for HTTP APIs proposed standard format.

Developers can use the built-in HttpProblems helper that is included with Zuplo to easily build standard error messages in custom policies.

For example, using the helper to return an unauthorized error on a custom authentication policy can be done as follows.

import { ZuploContext, ZuploRequest, HttpProblems } from "@zuplo/runtime";

export default async function (request: ZuploRequest, context: ZuploContext) {
  const isAuthorized = checkAuthorization(request);

  // Handle Error state
  if (!isAuthorized) {
    return HttpProblems.unauthorized(request, context);
  }

  return request;
}
ts

This will produce and error response in the standard format. Notice that trace information is included automatically. This makes it easy for users to report problems that can be searched in logs.

{
  "type": "https://httpproblems.com/http-status/401",
  "title": "Unauthorized",
  "status": 401,
  "instance": "/test",
  "trace": {
    "timestamp": "2023-07-16T17:13:31.352Z",
    "requestId": "28f2d802-8e27-49c8-970d-39d90ef0ac61",
    "buildId": "eb9ef87d-b55d-446e-9fdd-13c209c01b95",
    "rayId": "7e7be05256e53b60-IAD"
  }
}
json

The HttpProblems helper supports most every HTTP status code. Some additional examples are shown.

// General errors
HttpProblems.badRequest(request, context);
HttpProblems.internalServerError(request, context);

// Authorization errors
HttpProblems.unauthorized(request, context);
HttpProblems.forbidden(request, context);

// Success codes
HttpProblems.ok(request, context);
HttpProblems.created(request, context);
ts

#Overriding Property Values

Each method on the HttpProblems object supports overriding the default values of the problem response with custom values. The most common reason for this is for setting the detail value to something helpful for the end-user.

HttpProblems.badRequest(request, context, {
  detail: "Something was invalid about the request",
});
ts

Other properties like status and title can also be overridden, but make sure to do so within the rules of the spec.

The most important thing to remember about problem details is that every instance of a particular error should return the same value for title. Details about a specific error should go in the detail property.

An example of how to correctly use the title and detail properties can be demonstrated with an error that tells the user they provided an unexpected value for a query parameter called take that implements pagination. In this case, the title is always the same, but the detail value changes to provide the user with more detail about the error.

GET /widgets?take=1000
txt
HttpProblems.badRequest(request, context, {
  title: "Invalid value for query parameter 'take'",
  detail:
    "The take parameter must be less than 100. The provided value was 1000.",
});
ts
GET /widgets?take=hello
txt
HttpProblems.badRequest(request, context, {
  title: "Invalid value for query parameter 'take'",
  detail:
    "The take parameter must a number less than 100. The provided value was 'hello'",
});
ts

You can see how each of these cases help the user understand the problem, but still provide the same title.

#Additional Properties

It can sometimes be helpful to specify additional properties on the problem response. The problem specification requires a few specific fields, but allows for any additions as needed. For example, if we wanted to return an error to a user who was above their quote on creating widgets the error might look like this.

HttpProblems.badRequest(request, context, {
  title: "Failed to create widget. Over quota.",
  detail:
    "The account is over its quota for creating widgets. See the 'quota' field for details",
  quota: {
    currentlyUsed: 200,
    maxAllowed: 200,
    remaining: 0,
  },
});
ts

#Custom Headers

At times it can be useful to send custom headers with the error response. This can be done as shown below.

HttpProblems.badRequest(
  request,
  context,
  {
    detail: "Something was invalid about the request",
  },
  {
    "my-error-code": "230",
  },
);
ts

If you want to set headers without any overrides just pass undefined to the third argument.

HttpProblems.badRequest(request, context, undefined, {
  "my-error-code": "230",
});
ts