Files
crewli/api/app/Exceptions/FormBuilder/PublicFormApiException.php
bert.hausmans 53fe4d25a7 feat(form-builder): standardised error envelope for public form API (D6)
S2c D6. Seven concrete exceptions over a shared PublicFormApiException
base + a single renderer in bootstrap/app.php produce the contract:

  { "message": "...", "code": "...", "errors"?: {...} }

Codes: SCHEMA_NOT_FOUND (404), TOKEN_EXPIRED (410), TOKEN_REVOKED (410),
SCHEMA_UNPUBLISHED (410), SUBMISSION_ALREADY_SUBMITTED (409),
RATE_LIMITED (429 with Retry-After header), VALIDATION_FAILED (422
with per-field errors).

Used by PublicFormController (resolve) and PublicFormSubmissionController
(load/submit lifecycle). Every public-form endpoint now emits the same
envelope regardless of which branch failed; the renderer only fires on
PublicFormApiException so the authenticated API still uses its default
Laravel shapes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 22:55:44 +02:00

34 lines
803 B
PHP

<?php
declare(strict_types=1);
namespace App\Exceptions\FormBuilder;
use RuntimeException;
/**
* Base for every public form-builder API error. Renders as the S2c D6
* standardised envelope:
*
* { "message": "...", "code": "...", "errors"?: {...} }
*
* Concrete subclasses are tiny — they only set the machine code, HTTP
* status, and the user-facing message.
*/
abstract class PublicFormApiException extends RuntimeException
{
/** @var array<string, array<int, string>>|null */
public ?array $fieldErrors = null;
/** @var array<string, string> */
public array $headers = [];
public function __construct(
public readonly string $publicCode,
public readonly int $status,
string $message,
) {
parent::__construct($message);
}
}