Error handling
Use these rules when changing PHP exceptions and related API/frontend behavior. The application repository is the source of truth for actual throw sites, catch blocks, and consumers.
Choose an exception
Prefer an existing PHP, Nextcloud OCP, or LibreSign exception when it already represents the condition.
Use
LibresignExceptionfor an expected LibreSign error converted to an API/frontend response. New public typed exceptions must extend it so existing catch paths continue to work.Introduce a typed subclass for a meaningful condition that callers need to distinguish. Do not create a class solely to replace a test assertion.
Use a semantic code only when a client, frontend, or other caller needs a stable programmatic identifier. Define its constant near the producer.
For LibreSign-owned errors, do not identify a condition by its message; prefer a meaningful exception type or a consumed semantic code. For external errors with only text diagnostics, isolate and test any necessary matching.
Keep HTTP status separate
For new HTTP-facing errors, set
httpStatus:explicitly onLibresignException.InjectionMiddlewarereadsgetHttpStatus().Do not derive HTTP statuses from
getCode()in new code. Legacy codes may contain HTTP statuses, semantic identifiers, or frontend action markers; sethttpStatus:separately.For compatibility,
getHttpStatus()still falls back togetCode()(or HTTP 422 when the code is0). This legacy rule can propagate invalid statuses such as1; do not copy it into new call sites.
throw new LibresignException(
message: $message,
httpStatus: Http::STATUS_UNPROCESSABLE_ENTITY,
);
Preserve public contracts
Existing exception codes can also drive frontend actions:
SigningErrorHandlermaps code400to the create-signing-password action.PasswordToSignNotDefinedExceptionmust retain this code until its consumers are deliberately migrated. Moving it only tohttpStatus:would break the signing-password action.JSActions::ACTION_SIGN_ID_DOCis passed to the frontend through signing errors to identify the required identification-document action.
Before migrating a legacy code, follow its throw and catch paths and inspect
consumers such as SigningErrorHandler, ErrorPayloadBuilder and frontend
action mapping. Preserve messages, response payloads, HTTP/OCS statuses and
frontend actions unless a contract change is explicitly agreed and tested.
Migrate the legacy status fallback separately, with regression tests.
Test observable behavior
Assert meaningful exception types with
expectException()and stable consumed semantic codes withexpectExceptionCode().Keep message assertions when the text itself is the behavior being tested, such as a user-visible fallback or a TSA diagnostic.
For HTTP/OCS behavior, verify the final response contract for the supported OCS version, not only the input to a mocked response builder. For signing, test the frontend action contract.
Include a negative case showing that an unrelated error is not mistaken for the intended one, and a successful control when relevant.