fix(error): unify 4xx envelope and populate validation messages #231
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/pms-298-error-envelope"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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_envelopeaxum 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.
validatorleaves the message as None unless the field declares an explicitmessage = "...", so the old conversion emitted"message":"".humanize_field_errorsynthesises 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_entryforcesrequest.user_id = user.idfor 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