fix(contracts): validate billing_cycle and status at the request layer (422 not 500) (PMS-337) #254
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/PMS-337-contract-status-billing-validation"
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?
Background
Contract create/update accepted any
billing_cycleandstatusstring at the request layer. An out-of-set value passed deserialization (both fields are freeStrings) and only failed at the DB CHECK constraint inmigrations/009_contracts.sql, surfacing to the client as a 500DATABASE_ERRORinstead of a 422.CHECK sets (confirmed against
migrations/009_contracts.sql):status IN (draft, active, expired, cancelled, renewed);billing_cycle IN (monthly, quarterly, annually, one_time).Goal
Reject out-of-set
statusandbilling_cyclevalues with a 422 at the request layer, matching the existingvalidate_contract_typetreatment (PMS-299).Proposed approach
Add two custom validators in
src/utils/validation.rsmirroringvalidate_contract_type(const array plus membership check):validate_contract_status(constCONTRACT_STATUSES) andvalidate_billing_cycle(constBILLING_CYCLES). Wire them with#[validate(custom(function = ...))]ontoCreateContractRequest.status/.billing_cycle(bothString) and ontoUpdateContractRequest.status/.billing_cycle(bothOption<String>, where the custom validator runs on the inner value and skipsNone).Alternatives considered
A DB error mapper that translates the CHECK violation into a 422 was rejected: it would couple the error layer to constraint names and still round-trip to Postgres. Request-layer validation fails fast and matches the established pattern.
Acceptance criteria
validate_contract_statusandvalidate_billing_cycleadded tosrc/utils/validation.rs, kept in sync with the DB CHECK sets.cargo fmt --all --check,cargo test --lib, andcargo clippy --all-targets -- -D warningspass.