Low-probability edge case: a clean-EOF partial-JSON or interstitial 200 body is uncommon; the far more common mid-body drop already surfaces as Read (correctly retriable since #248).
Summary
In FcmTokenGenerator::mint, the OAuth token success body (HTTP 200) is read via parse_bounded_json_body. #248 fixed the BoundedJsonBodyError::Read variant to map to oauth_error_from_transport → Retriable. But the two sibling variants of the same read, Parse and TooLarge, still map to TokenAcquisitionError::permanent(...). A transient garbage-200 therefore drops the current FCM push with no retry (and, if persistent, fails every FCM push while recording a provider hard failure).
Location
src/push/fcm.rs:397-406 — the TooLarge and Parse arms of the success-body match in mint
Err(crate::push::BoundedJsonBodyError::TooLarge) => {
return Err(TokenAcquisitionError::permanent(Error::Fcm(...)));
}
Err(crate::push::BoundedJsonBodyError::Parse) => {
return Err(TokenAcquisitionError::permanent(Error::Fcm(...)));
}
Details
Permanent → into_send_attempt() → SendAttemptResult::Permanent → with_retry returns Err immediately with no retry → the notification is dropped and the dispatcher records a provider hard delivery failure.
Parse can arise transiently rather than from a genuinely malformed Google response:
- an intercepting proxy / captive portal returning a
200 with an HTML interstitial body, or
- a chunked-transfer success stream that ends cleanly (
chunk() → Ok(None)) after only partial JSON — a clean-EOF truncation surfaces as Parse, not Read.
TooLarge (>MAX_OAUTH_BODY_BYTES) is even less likely for a real OAuth response but has the same drop-without-retry consequence.
Because this is the mint path shared by every FCM send, a persistent garbage-200 also fails all FCM pushes.
Why this is not a duplicate
#248 resolved only the Read/IO variant of this exact success-body read. This is its two uncovered siblings. #277 is about OAuth non-2xx status classification, a different branch.
Recommendation
Map Parse/TooLarge on the OAuth success body to Retriable (consistent with #248's treatment of Read), so a transient interstitial/truncated 200 is retried rather than dropped. A genuinely malformed provider response will simply exhaust the retry budget and then fail.
Impact
One dropped FCM push per occurrence (or total FCM outage if persistent), gated on a rare transient-200 shape. LOW.
Low-probability edge case: a clean-EOF partial-JSON or interstitial
200body is uncommon; the far more common mid-body drop already surfaces asRead(correctly retriable since #248).Summary
In
FcmTokenGenerator::mint, the OAuth token success body (HTTP 200) is read viaparse_bounded_json_body. #248 fixed theBoundedJsonBodyError::Readvariant to map tooauth_error_from_transport→Retriable. But the two sibling variants of the same read,ParseandTooLarge, still map toTokenAcquisitionError::permanent(...). A transient garbage-200therefore drops the current FCM push with no retry (and, if persistent, fails every FCM push while recording a provider hard failure).Location
src/push/fcm.rs:397-406— theTooLargeandParsearms of the success-body match inmintDetails
Permanent→into_send_attempt()→SendAttemptResult::Permanent→with_retryreturnsErrimmediately with no retry → the notification is dropped and the dispatcher records a provider hard delivery failure.Parsecan arise transiently rather than from a genuinely malformed Google response:200with an HTML interstitial body, orchunk()→Ok(None)) after only partial JSON — a clean-EOF truncation surfaces asParse, notRead.TooLarge(>MAX_OAUTH_BODY_BYTES) is even less likely for a real OAuth response but has the same drop-without-retry consequence.Because this is the mint path shared by every FCM send, a persistent garbage-
200also fails all FCM pushes.Why this is not a duplicate
#248 resolved only the
Read/IO variant of this exact success-body read. This is its two uncovered siblings. #277 is about OAuth non-2xx status classification, a different branch.Recommendation
Map
Parse/TooLargeon the OAuth success body toRetriable(consistent with #248's treatment ofRead), so a transient interstitial/truncated200is retried rather than dropped. A genuinely malformed provider response will simply exhaust the retry budget and then fail.Impact
One dropped FCM push per occurrence (or total FCM outage if persistent), gated on a rare transient-200 shape. LOW.