75 lines
3.8 KiB
Markdown
75 lines
3.8 KiB
Markdown
# Client-Safe Error Boundary Design
|
|
|
|
**Date:** 2026-08-02
|
|
**Status:** approved by the user's instruction to apply the detailed P1/P2 review sequentially
|
|
**Scope:** HTTP error envelopes in `adapter:inbound:web` and the `sample-portfolio` domain advice
|
|
|
|
## Context
|
|
|
|
Several handlers pass `Exception#getMessage()`, rejected request values, or a raw request URL into
|
|
the public error envelope. Those values are not a stable API contract and can contain identifiers,
|
|
tokens, uploaded values, configuration details, or internal diagnostics. Persistence and outbound
|
|
dependency failures already use fixed client-safe messages; the rest of the HTTP boundary must
|
|
follow the same rule.
|
|
|
|
## Decision
|
|
|
|
The inbound adapter owns a message allowlist keyed by stable error code. Handlers may expose only:
|
|
|
|
- stable `code`, `category`, HTTP status, and `retryable` from `ApiErrorCode`;
|
|
- fixed, code-specific client messages;
|
|
- bounded structural details such as field name, validation reason code, expected Java type,
|
|
supported HTTP methods, or supported media types.
|
|
|
|
They must not expose exception messages, rejected values, raw request URLs, adapter/configuration
|
|
diagnostics, opaque cursors, authentication diagnostics, resource identifiers, or duplicate domain
|
|
values. Bean Validation interpolated/default messages are also discarded because custom templates
|
|
can include the validated value. Validation details contain only normalized server-owned property
|
|
names plus allowlisted reason codes and fixed messages; collection/map keys and indices are removed.
|
|
|
|
`ClientSafeErrorMessages` is extended for skeleton-wide operational codes. The sample keeps its
|
|
domain wording in a separate package-private `PortfolioClientSafeErrorMessages`, preserving the
|
|
rule that production modules do not know sample business concepts.
|
|
|
|
## Public Messages
|
|
|
|
Representative mappings are fixed as follows:
|
|
|
|
- `MAPPING_FAILED` → `Request data could not be mapped`;
|
|
- `BAD_PARAMETER` → `Request parameter is invalid`;
|
|
- `INVALID_TOKEN` → `Authentication token is invalid`;
|
|
- `UNAUTHENTICATED` → `Authentication is required`;
|
|
- authorization denials → `Access is denied`;
|
|
- `PRECONDITION_FAILED` → `Resource state changed; refresh and retry`;
|
|
- page/cursor failures → generic corrective text, with safe field/reason details retained;
|
|
- `ADAPTER_DISABLED` and internal classifications → `Internal server error`;
|
|
- domain not-found/conflict/invariant codes → fixed noun-level text with no ID/title value.
|
|
|
|
Transport overrides use fixed wording and retain only safe protocol metadata. For example, 405
|
|
still emits `Allow`, while both controller-route (`NoHandlerFoundException`) and static-resource
|
|
(`NoResourceFoundException`) 404s use the same envelope without echoing the request URL.
|
|
|
|
## Testing
|
|
|
|
Tests inject conspicuous secret sentinels into exception messages, rejected values, URLs, tokens,
|
|
IDs, and duplicate titles. Every resulting response must preserve its status/code/category while
|
|
excluding the sentinel from both `error.message` and `error.details`.
|
|
|
|
Validation tests additionally place sentinels in interpolated/default messages and iterable
|
|
keys/indices. A real MockMvc resource-resolution request verifies the Spring 7
|
|
`NoResourceFoundException` path rather than calling the advice method directly.
|
|
|
|
The focused module suites remain the primary verification:
|
|
|
|
- `:adapter:inbound:web:test` for operational and transport handlers;
|
|
- `:sample-portfolio:test` for domain advice and sample wire behavior;
|
|
- `verifyCleanArchitectureDependencies` for dependency direction.
|
|
|
|
## Non-Goals
|
|
|
|
- no change to error codes, categories, statuses, or retryability;
|
|
- no suppression of server-side logs or tracing in this batch;
|
|
- no application/domain dependency on HTTP response types;
|
|
- no generic exception-message sanitizer based on regexes or truncation;
|
|
- no staging, commit, amend, or push by an agent.
|