fix(web): make API response decoding skew-tolerant #507
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/BUNYIP-506-skew-tolerant-decoding"
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?
An unrelated field was load-bearing on the auth path. bunyip-web v0.12.0 declared
subscription_tieras a required field, bunyip-api v0.13.0 renamed it tomembership_tier, and every decode of the user object failed with "missing fieldsubscription_tier". Login step 1 passed (parse_login returns TwoFactorRequired before it decodes AuthResponse), so the failure surfaced on the Two-Factor Authentication screen while the user was typing a TOTP code. A membership tier has nothing to do with verifying a TOTP code.bunyip-web/src/api/types.rs::Usernow requires only id, email, role, email_verified and two_factor_enabled; every other field carries#[serde(default)]with a least-privileged default (no membership, no tier, no price lock), so an AuthResponse cannot fail to decode on billing or presentation data. The same rule now covers every Deserialize struct in the file: a field either defaults or is listed in the ESSENTIAL_FIELDS table of the newscripts/check-serde-compat.nuwith the reason a default there would be a lie. The guard runs in check.yml with a --self-test first, so it cannot pass vacuously. The rule is asymmetric by direction: request structs under bunyip-api/src/handlers/ keep their required inputs required, and a new test asserts a login body with no password is still rejected.UserRole, MembershipStatus, MembershipTier and FeedbackStatus decode through String via a
wire_enum!macro and gain an Unknown variant, so a value a newer API adds renders neutrally instead of failing the response with "unknown variant" (#[serde(other)]is not available: serde allows it only on internally or adjacently tagged enums). UserRole::Unknown never compares equal to Admin, and a render-level test asserts the dashboard shell hands it no admin link. User.membership_tier and AdminUser.membership_tier carry#[serde(alias = "subscription_tier")]for the reverse skew of a new web against a not-yet-restarted API; the alias is the expand half of an expand/contract rename and drops in v0.15.0.All four decode sites now log
tracing::error!with the endpoint, the target type and the serde message (a 2xx the client cannot read is a deployment defect, not a routine event), and ApiError carries one fixed user-facing line for DECODE_ERROR, so serde internals stop rendering in the login and 2FA forms.#BUNYIP-506