Skip to content
Nav

API errors

composer require langsys/openapi-docs-generator
Packagist version Monthly downloads License PHP 8.1+ Laravel 10 / 11

Document machine-readable API errors from Spatie Data classes, one class per failure mode. Point the generator at your abstract error base class:

'errors' => [
'base_class' => \App\Http\Errors\ApiError::class,
],
abstract class ApiError extends Data {}
class NotFoundError extends ApiError
{
public const CODE = 'not_found';
public const MESSAGE = 'Resource not found';
public const STATUS = 404; // or an int-backed enum case
}
class ProjectNotFoundError extends NotFoundError
{
public const CODE = 'project_not_found';
public const MESSAGE = 'No project with that id';
// STATUS is inherited from NotFoundError
}
ConstantTypeRule
CODEstringThe slug clients branch on. Every concrete error class declares its own.
MESSAGEstringThe default human message, and the documentation text. May contain {marker} placeholders.
STATUSint, or an int-backed enumThe HTTP status. May be inherited.

Generation fails before anything is written when:

  • a concrete error class inherits CODE or MESSAGE instead of declaring it;
  • two error classes declare the same CODE;
  • no STATUS can be resolved, or it is not an HTTP status between 100 and 599;
  • an error class carries a class-level #[Description] (MESSAGE already documents it);
  • an #[EnvelopeField] property reuses an error-object field name;
  • MESSAGE contains a {marker} that names no public property of the class.

Public properties are the error’s typed details. #[EnvelopeField] lifts a property to the top level of the error object (e.g. a validation errors map). #[Throws] on a method or controller class declares which errors an action can return.

{
"status": false,
"data": [],
"error": {
"message": "Insufficient balance to complete this request",
"code": "insufficient_balance",
"template": "Insufficient balance to complete this request",
"details": { "required": 500, "available": 120 }
}
}

The data key suits apps whose success and error responses share one envelope. If yours doesn’t, set response_fields.data to null and error responses become { status, error }.

For each referenced error the generator emits:

  • InsufficientBalanceError — details schema (omitted when there are none)
  • InsufficientBalanceErrorBody — the error object
  • InsufficientBalanceErrorResponse — the envelope
  • components.responses.InsufficientBalanceError — reusable response with x-http-status
  • ErrorCode — string enum of the referenced codes

Only referenced errors are documented. An error no operation can return is an internal failure mode, not API surface.

MESSAGE may contain {marker} placeholders so a client can translate it and then fill in the values. A marker must name a public property of the class. The error body then carries template (verbatim), params (one property per marker), and message (filled in).

#[Throws] on the action is the explicit list. On a controller class it applies to every action:

use Langsys\OpenApiDocsGenerator\Generators\Attributes\Throws;
class ProjectController
{
#[OA\Post(path: '/api/projects/{project}/purchase', responses: [...])]
#[Throws(InsufficientBalanceError::class, ValidationError::class)]
public function purchase(Project $project, PurchaseRequest $request) { … }
}

Errors can also be implied so they never have to be repeated. Configure implied_errors per documentation set:

'implied_errors' => [
'middleware' => [
'auth:sanctum' => [UnauthenticatedError::class],
'deduct.request' => [InsufficientBalanceError::class],
],
'validation' => ValidationFailedError::class,
'not_found' => NotFoundError::class,
'not_found_binding' => 'model', // or 'any'
],

When a convention lives somewhere the framework cannot prove, write a rule implementing Contracts\ImpliedErrorRule. Middleware is matched against the route’s fully-resolved middleware — the same ground truth filtered sets use.

An operation’s errors are the union of all sources, deduplicated by class, then grouped by HTTP status. One error for a status emits a $ref; several emit an inline oneOf with a code discriminator. A response the author wrote for that status always wins.

Feedback