Skip to content

refactor(ads/admob): propagate FailureReason through poller and dispatcher#6842

Merged
polmiro merged 2 commits into
mainfrom
pol/ads-admob-poller-failure-reason
May 27, 2026
Merged

refactor(ads/admob): propagate FailureReason through poller and dispatcher#6842
polmiro merged 2 commits into
mainfrom
pol/ads-admob-poller-failure-reason

Conversation

@polmiro

@polmiro polmiro commented May 26, 2026

Copy link
Copy Markdown
Member

Checklist

  • Unit tests (Outcome / Poller / Dispatcher assertions tightened to the specific FailureReason per classification path)
  • No public API impact — internal-only refactor

Motivation

The verification polling loop currently collapses every non-verified terminal state into a single Outcome.failed case. As a result, downstream consumers can't tell why verification failed — timeout, backend rejection, network noise, or something unrecognised. Carrying that information out of Poller opens the door for surfacing a structured failure reason on future telemetry / events without changing the public RewardVerificationResult.failed.

Description

  • RewardVerification.Outcome.failed gains an associated FailureReason payload (.timeout / .backendError / .unknown).
  • Poller.run classifies failures:
    • Loop budget exhausted (all attempts pending, transient throws, or unknown statuses) → .failed(.timeout).
    • Backend returns explicit .failed status → .failed(.backendError).
    • Non-transient ErrorCode throw → .failed(.backendError).
    • Any other throw (CancellationError, unrecognised error types) → .failed(.unknown).
    • Task.isCancelled before an attempt → .failed(.unknown). Dispatcher separately swallows the delivery on real cancellation, so this is only observable in unit tests.
  • Dispatcher's logDescription includes the reason for debugging.
  • mapOutcome in RewardedAds+RewardVerification continues to pattern-match .failed without binding, so the public RewardVerificationResult.failed stays parameter-less.

Test plan

  • swift build (adapter + tests) + swiftlint clean.
  • OutcomeTests: case construction asserts the new associated value; exhaustive-switch test enumerates all three FailureReason variants.
  • PollerTests: every previously bare case .failed = outcome now asserts the specific reason produced by that classification path.
  • DispatcherTests: same tightening.
  • Stale comment on the cancellation test updated to reflect the new distinction (CancellationError is not an ErrorCode, so it falls through to the catch-all and surfaces .unknown).

Note

Low Risk
Internal-only typing and classification in the AdMob adapter; public reward verification results are unchanged.

Overview
Internal AdMob reward verification now attaches a FailureReason (.timeout, .backendError, .unknown) to Outcome.failed instead of a bare failure case, so the polling loop can distinguish why SSV did not succeed.

Poller.run maps outcomes: exhausted attempt budget → .timeout; explicit backend .failed status or non-transient ErrorCode.backendError; cancellation before polling, CancellationError, and other unclassified throws → .unknown. Dispatcher logging includes the reason in logDescription.

mapOutcome still collapses any .failed to the public parameter-less RewardVerificationResult.failed, so there is no app-facing API change—only richer internal signal for future telemetry. Unit tests assert the specific reason on each path.

Reviewed by Cursor Bugbot for commit 84d05d0. Bugbot is set up for automated code reviews on this repo. Configure here.

…tcher

Internal-only change to carry a structured failure reason out of the
verification polling loop. No behavior change to the public
RewardVerificationResult.failed, which stays parameter-less — mapOutcome
in RewardedAds+RewardVerification continues to discard the reason. The
new payload becomes useful when an upcoming change exposes failure
reasons on a reward-failed-to-verify event.

Outcome:
- .failed gains an associated FailureReason payload (.timeout /
  .backendError / .unknown).

Poller.run classification:
- Loop budget exhausted (all attempts pending, transient throws, or
  unknown statuses): .failed(.timeout).
- Backend returns an explicit .failed status: .failed(.backendError).
- Non-transient ErrorCode throw: .failed(.backendError).
- Any other throw (CancellationError, unrecognised error types):
  .failed(.unknown).
- Task.isCancelled before an attempt: .failed(.unknown). Dispatcher
  separately swallows the delivery on real cancellation, so this is
  only observable in tests.

Tests tightened: Outcome / Poller / Dispatcher tests now assert the
specific FailureReason for each classification path. Stale comment on
testCancellationErrorFromPollStatusReturnsFailedWithoutRetrying
(claiming Poller didn't distinguish CancellationError from non-transient
throws) updated.
@polmiro polmiro requested a review from ajpallares May 26, 2026 16:17
@polmiro polmiro marked this pull request as ready for review May 26, 2026 16:18
@polmiro polmiro requested a review from a team as a code owner May 26, 2026 16:18

@ajpallares ajpallares left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes make sense, but I don't see the new FailureReason being read anywhere (other than tests). How is this going to be used? I wonder mostly in case we need to expose something else or differently

@ajpallares ajpallares left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me!

Comment thread AdapterSDKs/RevenueCatAdMob/Tests/RevenueCatAdMobTests/OutcomeTests.swift Outdated
Drop redundant Outcome equality test (per review feedback) and pass a
FailureReason when constructing .failed in PresentMappingTests.

@peterporfy peterporfy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks great!

@polmiro polmiro enabled auto-merge (squash) May 27, 2026 07:49
@polmiro polmiro merged commit b480c92 into main May 27, 2026
17 of 19 checks passed
@polmiro polmiro deleted the pol/ads-admob-poller-failure-reason branch May 27, 2026 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants