fix(error): unify 4xx envelope and populate validation messages #231

Merged
David merged 1 commit from fix/pms-298-error-envelope into main 2026-06-14 15:54:44 +02:00
Owner

Body-deserialization failures (missing field, wrong type, unknown enum variant) previously returned raw axum/serde plaintext with status 422, which broke any consumer expecting the {error:{code,message}} envelope and leaked internal serde detail, field names, and line/column offsets. Add a normalize_error_envelope axum middleware, layered outermost on both the PSA and portal routers, that rewrites any non-JSON 4xx (extractor rejections such as a JSON body that fails to deserialize) into the canonical envelope while passing every JSON AppError response through untouched. The original plaintext body is discarded so no "Failed to deserialize the JSON body..." text is ever returned.

Validation errors now carry a non-empty, human-readable per-field message. validator leaves the message as None unless the field declares an explicit message = "...", so the old conversion emitted "message":"". humanize_field_error synthesises a message from the failing constraint's code and params, including the bound (e.g. "must be at most 255 characters") to satisfy the constraint-bound requirement. The top-level message no longer doubles: the Validation Display already prefixes "Validation failed: ", so the stored message changed from "Validation failed" (which produced "Validation failed: Validation failed") to "one or more fields are invalid".

Time-entry owner is already validated against the auth token: create_time_entry forces request.user_id = user.id for non-admins, so a non-admin cannot attribute an entry to an arbitrary user. No behavior change there; the missing-field deserialization message it emitted is now enveloped by the middleware above.

#PMS-298

Body-deserialization failures (missing field, wrong type, unknown enum variant) previously returned raw axum/serde plaintext with status 422, which broke any consumer expecting the {error:{code,message}} envelope and leaked internal serde detail, field names, and line/column offsets. Add a `normalize_error_envelope` axum middleware, layered outermost on both the PSA and portal routers, that rewrites any non-JSON 4xx (extractor rejections such as a JSON body that fails to deserialize) into the canonical envelope while passing every JSON AppError response through untouched. The original plaintext body is discarded so no "Failed to deserialize the JSON body..." text is ever returned. Validation errors now carry a non-empty, human-readable per-field message. `validator` leaves the message as None unless the field declares an explicit `message = "..."`, so the old conversion emitted `"message":""`. `humanize_field_error` synthesises a message from the failing constraint's code and params, including the bound (e.g. "must be at most 255 characters") to satisfy the constraint-bound requirement. The top-level message no longer doubles: the Validation Display already prefixes "Validation failed: ", so the stored message changed from "Validation failed" (which produced "Validation failed: Validation failed") to "one or more fields are invalid". Time-entry owner is already validated against the auth token: `create_time_entry` forces `request.user_id = user.id` for non-admins, so a non-admin cannot attribute an entry to an arbitrary user. No behavior change there; the missing-field deserialization message it emitted is now enveloped by the middleware above. #PMS-298
fix(error): unify 4xx envelope and populate validation messages
All checks were successful
E2E / Playwright against staging (pull_request) Successful in 37s
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 1m2s
Integration / integration tests (pull_request) Successful in 2m50s
Create release / Create release from merged PR (pull_request) Has been skipped
8694729ef5
Body-deserialization failures (missing field, wrong type, unknown enum variant) previously returned raw axum/serde plaintext with status 422, which broke any consumer expecting the {error:{code,message}} envelope and leaked internal serde detail, field names, and line/column offsets. Add a `normalize_error_envelope` axum middleware, layered outermost on both the PSA and portal routers, that rewrites any non-JSON 4xx (extractor rejections such as a JSON body that fails to deserialize) into the canonical envelope while passing every JSON AppError response through untouched. The original plaintext body is discarded so no "Failed to deserialize the JSON body..." text is ever returned.

Validation errors now carry a non-empty, human-readable per-field message. `validator` leaves the message as None unless the field declares an explicit `message = "..."`, so the old conversion emitted `"message":""`. `humanize_field_error` synthesises a message from the failing constraint's code and params, including the bound (e.g. "must be at most 255 characters") to satisfy the constraint-bound requirement. The top-level message no longer doubles: the Validation Display already prefixes "Validation failed: ", so the stored message changed from "Validation failed" (which produced "Validation failed: Validation failed") to "one or more fields are invalid".

Time-entry owner is already validated against the auth token: `create_time_entry` forces `request.user_id = user.id` for non-admins, so a non-admin cannot attribute an entry to an arbitrary user. No behavior change there; the missing-field deserialization message it emitted is now enveloped by the middleware above.

#PMS-298
David merged commit a7e5bad69c into main 2026-06-14 15:54:44 +02:00
David deleted branch fix/pms-298-error-envelope 2026-06-14 15:54:44 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!231
No description provided.