The objective of this package is to facilitate error outputs from API requests in accordance with the RFC 9457 standard.
It transforms error outputs into json format with the following characteristics:
- header:
Content-Type: application/problem+json
- response:
type
: URI that identifies the type of error that occured, for examplehttps://example.com/validation-error
.title
: Human-readable identifier, usually the same type field should have the same title field alongside. An example would be something like Form validation failed.status
: A copy of the HTTP status code.detail
: More information about the specific problem, and if it's appropriate also steps to correct it. For example information about a form validation problem Username is already taken, please choose a different username.instance
: An identifier for this specific occurence, which may not be useful to the client but may be included in a bug report for example.- Additional fields: Any fields may be added to give additional information, and consuming clients are expected to ignore any that they don't have specific support for.
timestamp
: (RFC 9457 enhancement): A timestamp indicating when the problem was generated. This helps in logging and tracing the error, especially in systems where timing is critical.
Example:
Server Response:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
Content-Language: en
{
"status": 422,
"type": "https://example.test/validation-error",
"title": "Form validation failed",
"detail": "One or more fields have validation errors. Please check and try again.",
"instance": "http://example.test/api/test/1",
"timestamp": "2024-02-09T11:55:51.252968Z",
"errors": [
{
"name": "username",
"reason": "Username is already taken."
},
{
"name": "email",
"reason": "Email format is invalid."
}
]
}
You can install the package via composer:
composer require pedrosalpr/laravel-api-problem
To use it, just go to the register
method within Exceptions\Handler.php
and add the following code:
use Pedrosalpr\LaravelApiProblem\LaravelApiProblem;
public function register(): void
{
...
$this->renderable(function (\Throwable $e, Request $request) {
if ($request->is('api/*') || $this->shouldReturnJson($request, $e)) {
$apiProblem = new LaravelApiProblem($e, $request);
return $apiProblem->render();
}
});
}
If you want to debug, just add the following line before the return:
dd($apiProblem->toDebuggableArray());
To use it, add the following code to the Exception Closure in the bootstrap/app.php
file:
use Pedrosalpr\LaravelApiProblem\LaravelApiProblem;
...
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (\Throwable $e, Request $request) {
if ($request->is('api/*') || $this->shouldReturnJson($request, $e)) {
$apiProblem = new LaravelApiProblem($e, $request);
return $apiProblem->render();
}
});
})...
There is the possibility of creating exceptions that extend LaravelApiProblemException
.
This already makes it easier to transform the exception into the Api Problem pattern.
To do this, simply run the following command:
php artisan laravel-api-problem:exception {name}
For example:
php artisan laravel-api-problem:exception DummyException
This creates a class exception inside Exceptions\ApiProblem
;
<?php
namespace App\Exceptions\ApiProblem;
use Pedrosalpr\LaravelApiProblem\Exceptions\LaravelApiProblemException;
class DummyException extends LaravelApiProblemException
{
}
If you want to customize an Api Problem
class to add your guidelines for which error responses should be returned, simply extend the class with the following command:
php artisan laravel-api-problem:extend
For example:
php artisan laravel-api-problem:extend DummyApiProblem
This creates a class Api Problem inside ApiProblem
;
<?php
namespace App\ApiProblem;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Pedrosalpr\LaravelApiProblem\Http\LaravelHttpApiProblem;
use Pedrosalpr\LaravelApiProblem\LaravelApiProblem;
class DummyApiProblem extends LaravelApiProblem
{
public function __construct(
protected \Throwable $exception,
protected Request $request
) {
match (get_class($exception)) {
\Exception::class => $this->dummy(),
default => parent::__construct($exception, $request)
};
}
protected function dummy()
{
$extensions = [
'errors' => "Dummy",
];
$this->apiProblem = new LaravelHttpApiProblem(
Response::HTTP_I_AM_A_TEAPOT,
$this->exception->getMessage(),
$this->getUriInstance(),
$extensions
);
}
}
And within the match, add the names of the exceptions classes with their respective methods, such as dummy()
.
In the Handler.php
file replace the LaravelApiProblem
object instance to DummyApiProblem
.
$this->renderable(function (\Throwable $e, Request $request) {
if ($request->is('api/*') || $this->shouldReturnJson($request, $e)) {
$apiProblem = new DummyApiProblem($e, $request);
return $apiProblem->render();
}
});
Add the following code to the Exception Closure in the bootstrap/app.php
file:
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (\Throwable $e, Request $request) {
if ($request->is('api/*') || $this->shouldReturnJson($request, $e)) {
$apiProblem = new DummyApiProblem($e, $request);
return $apiProblem->render();
}
});
})
TODO
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.