feat(form-builder): FormBindingExceptionClassifier helper

Per RFC-WS-6 §Q3 v1.3 addition 2.

Centralises the Throwable -> failure_response_code mapping so the
listener (ApplyBindingsOnFormSubmit::handle catch block) and the
retry-service (FormFailureRetryService::recordFailure) produce
identical classifications. Single behaviour-change point.

Resolution order: FormBindingApplicatorException subclass dispatch via
reasonCode(); fallback 'unknown_error' for anything outside the hierarchy.

Wiring into the listener and the retry service lands in D2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-08 01:59:32 +02:00
parent b7bd7904c2
commit 1f66fef3c8

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace App\FormBuilder\Bindings;
use App\Exceptions\FormBuilder\FormBindingApplicatorException;
use Throwable;
/**
* Maps a Throwable to a failure_response_code string.
*
* Used by both ApplyBindingsOnFormSubmit::handle's catch block (D2) and
* FormFailureRetryService::recordFailure (D2 update). Centralised so the
* listener and the retry-service produce identical classifications and a
* single behaviour change requires a single edit.
*
* Resolution order:
* 1. If the Throwable is a FormBindingApplicatorException, return its reasonCode().
* Subclass dispatch handles SchemaConfig / Infra / DataIntegrity / Timeout
* (Timeout extends Infra so it inherits 'temporary_error').
* 2. Otherwise, return 'unknown_error' anything outside the hierarchy
* (database connection lost not surfaced as Infra, generic RuntimeException
* from a non-applicator code path, IdentityMatchInvariantViolation if it
* somehow leaks here) is unknown from the response-shape perspective.
*
* Per RFC-WS-6 §Q3 v1.3 addition 2.
*/
final class FormBindingExceptionClassifier
{
public static function classify(Throwable $exception): string
{
if ($exception instanceof FormBindingApplicatorException) {
return $exception->reasonCode();
}
return 'unknown_error';
}
}