Skip to main content

Problem documents

An API that answers its errors in a shape of its own makes every client learn that shape. RFC 9457 defines one shape for all of them, the problem document, served as application/problem+json:

{
"type": "about:blank",
"title": "Not Found",
"status": 404
}

type identifies the kind of problem and title names it, the same for every occurrence. detail says what happened this time and instance which occurrence it was. A problem without a type of its own is about:blank, and its title is the phrase of its status code.

Which requests get one​

The package answers the failures of an API request with a problem document: a request under one of the prefixes in http-api.problems.route_prefixes, by default api, and a request that expects JSON or asks for application/problem+json. Every other request is answered as Laravel answers it, so an HTML page stays an HTML page.

The failureThe answer
a validation422, or the status the validation was given, with the fields and their messages in errors
an unauthenticated request401, with WWW-Authenticate: Bearer, the scheme http-api.problems.www_authenticate names
an HTTP exception, abort(403, '…') and a throttled request among themits own status and headers, Retry-After included, and the message it was thrown with as detail
a refused authorization403, or the status the policy gave it, with the policy's answer as detail
a missing model or a missing route404, without the message of a missing model, which names its class and its key
an exception that provides its own problemthat problem, see below
an exception that carries its own response, and a validation given onethat response, as it is
anything else500, with a sentence and nothing the exception said

With app.debug on, an exception nobody anticipated is left to Laravel, whose debug answer is what a developer wants to read while building.

A problem of your own​

An exception that implements ProvidesProblem is answered with the document it returns:

use Pushery\HttpApi\Problems\Problem;
use Pushery\HttpApi\Problems\ProvidesProblem;

final class OutOfStock extends RuntimeException implements ProvidesProblem
{
public function toProblem(): Problem
{
return new Problem(
409,
type: 'https://example.com/problems/out-of-stock',
title: 'Out of stock',
detail: 'Two of the items are no longer available.',
members: ['items' => [3, 7]],
);
}
}

Members beside the five standard ones are extension members, written after them. An extension member can never replace a standard one, so a member named status cannot change the status a client reads.

A controller that answers a problem without throwing returns one directly:

use Pushery\HttpApi\Problems\Problem;

return (new Problem(429, detail: 'Try again in a minute.', headers: ['Retry-After' => '60']))->toResponse();

The package's own problems​

The problems the idempotency middleware answers with carry a type of their own, which starts with http-api.problems.type_base. By default that is the page that documents each one:

Switching it off​

HTTP_API_PROBLEMS=false leaves every failure to Laravel. An application with an exception handler of its own, one without Laravel's render callbacks, gets no renderer from the package and calls ProblemRenderer::render() itself, which answers a failure or returns null for a request that is no API request.