Skip to content

Replace print with Logger.debug in ISODurationFormatter#6691

Merged
facumenzella merged 4 commits into
mainfrom
fix/iso-duration-logger
Apr 24, 2026
Merged

Replace print with Logger.debug in ISODurationFormatter#6691
facumenzella merged 4 commits into
mainfrom
fix/iso-duration-logger

Conversation

@facumenzella

@facumenzella facumenzella commented Apr 24, 2026

Copy link
Copy Markdown
Member

Summary

  • ISODurationFormatter.parse(from:) was using a raw print() on parse failure, bypassing the SDK's logging infrastructure. Replaced with Logger.error().

A bare print in an SDK outputs unconditionally to the user's console in production builds with no way to filter or silence it. Logger.error integrates with the SDK log level, respects any log suppression, and is consistent with error logging everywhere else in Sources/.

Test plan

  • swift build passes (verified locally)
  • No logic change — same message, same return path

🤖 Generated with Claude Code


Note

Low Risk
Low risk: only replaces a print side-effect with structured SDK logging, with no behavior change beyond how the message is emitted.

Overview
Updates ISODurationFormatter.parse(from:) to log ISO 8601 duration parse failures via the SDK Logger instead of using print, so output respects log level/suppression.

Adds a new CodableStrings.failed_to_parse_duration log message to keep the warning text centralized and consistent.

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

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@facumenzella facumenzella requested a review from a team as a code owner April 24, 2026 11:29
@facumenzella facumenzella added the pr:fix A bug fix label Apr 24, 2026
@facumenzella facumenzella enabled auto-merge (squash) April 24, 2026 11:29
Comment thread Sources/Misc/DateAndTime/ISODurationFormatter.swift Outdated
@facumenzella facumenzella changed the title Replace print with Logger.error in ISODurationFormatter Replace print with Logger.debug in ISODurationFormatter Apr 24, 2026
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1688f41. Configure here.


guard let match = match else {
print("Failed to parse ISO duration: \(periodString)")
Logger.warn(Strings.codable.failed_to_parse_duration(periodString: periodString))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Wrong log level: warn used instead of debug

Low Severity

The PR title says "Replace print with Logger.debug" and the reviewer explicitly suggested Logger.debug, but the code uses Logger.warn. A failed ISO duration parse is a decoding-level detail that would generate unnecessary noise at the warn level in production logs. Logger.debug is the appropriate level here, consistent with the stated intent and reviewer feedback.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 1688f41. Configure here.


guard let match = match else {
print("Failed to parse ISO duration: \(periodString)")
Logger.warn(Strings.codable.failed_to_parse_duration(periodString: periodString))

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.

[Not for this PR]
Wonder if we should add a linter to avoid this from happening in the future in production code at least...

@facumenzella facumenzella merged commit 03c390e into main Apr 24, 2026
16 of 18 checks passed
@facumenzella facumenzella deleted the fix/iso-duration-logger branch April 24, 2026 13:51
JZDesign pushed a commit that referenced this pull request Apr 24, 2026
* Replace print with Logger.error in ISODurationFormatter (#6691)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Decode reward payload in RewardVerification poll response (#6678)

* Decode reward payload in RewardVerification poll response

Plumbs the verified-reward payload from the backend response through the
internal `pollRewardVerificationStatus` SPI so RC ad adapters can dispatch
meaningful "verified" events with the granted reward.

* Adds `VirtualCurrencyReward` and `VerifiedReward` (`@_spi(Internal) public`)
  under `Sources/Ads/RewardVerification/`.
* Extends `RewardVerificationStatusResponse` to optionally decode a typed nested
  reward payload (`{ type, code, amount }`) on `verified` responses.
  - `reward` absent or null → `.noReward`
  - `type == "virtual_currency"` with valid `code`+`amount` → `.virtualCurrency`
  - any other `type` (or malformed `virtual_currency`) → `.unsupportedReward` + warn
* `RewardVerificationPollStatus.verified` now carries the `VerifiedReward`.
* `Purchases.pollRewardVerificationStatus` returns `.verified(reward)`.
* Test mocks injected via a new `BasePurchasesTests.MockBackend` convenience
  init so `backend.adsAPI` resolves to `MockAdsAPI`.

* Add unit tests for VerifiedReward and VirtualCurrencyReward

Co-locates the unit tests next to the types this PR introduces under
`Sources/Ads/RewardVerification/`:

- `Tests/UnitTests/Ads/RewardVerification/VirtualCurrencyRewardTests.swift`
  Covers field storage, equality (both fields must match), and decimal
  precision preservation.

- `Tests/UnitTests/Ads/RewardVerification/VerifiedRewardTests.swift`
  Covers `.virtualCurrency(_)` associated-value carrying, `.noReward`
  vs `.unsupportedReward` distinct-case identity, equality (matching
  associated reward required), and an exhaustive switch coverage test
  to guard against silently adding cases.

Both tests pass against the existing `Sources/Ads/RewardVerification/`
types and require no changes to them.

(Originally written on top of #6663; moved to this base PR so the type
and its tests ship together rather than splitting across PRs.)

* fix: warn when reward verification reward value is not a JSON object

Previously, `decodeVerifiedReward` silently returned `.noReward` for any
of: missing reward key, null reward, or non-object reward value (e.g. a
string/number/array). The first two are expected; the last indicates a
backend mismatch and should be surfaced.

Now distinguish:
- absent or null reward → `.noReward` (silent, expected)
- present but not a JSON object → `.unsupportedReward` + `Logger.warn`

This matches the existing convention in this file of logging warnings
when decoding unrecognized/malformed values into fallback cases.

* Fix virtual currency reward amount type to integer.

Align reward payload modeling with backend semantics by decoding `amount` as Int and updating tests to reject fractional values as malformed rewards.

* fix: address review — couple verified status to reward

Make the reward payload part of the verified status case so invalid state combinations are unrepresentable and remove the verified fallback mapping.

* fix: address review — reject non-positive reward amounts

Treat virtual-currency rewards with non-positive amounts as malformed payloads and return unsupported reward with warning logs.

* fix: address review — reject empty reward currency code

Validate virtual-currency payloads require a non-empty code and treat empty code values as malformed unsupported rewards.

* fix: address review — remove non-actionable enum switch test

Drop the exhaustive switch smoke test in VerifiedRewardTests since it did not validate behavior and duplicated compiler guarantees.

* fix: address review — remove synthesized equatable assertions

Drop VerifiedReward tests that only mirrored compiler-synthesized Equatable behavior and keep behavior-focused coverage.

* Delete claude.yml workflow (#6688)

* Add workflowTrigger to ButtonComponent.Action (#6693)

* Add workflowTrigger case to ButtonComponent.Action

Mirrors Android's rename of Action.Workflow → Action.WorkflowTrigger
(purchases-android#3380). Decodes backend "type": "workflow" into
.workflowTrigger so workflow buttons are rendered (previously decoded
as .unknown and hidden) and track as "workflow_trigger" in analytics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add tests for workflowTrigger action decoding and analytics value

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Cache decoded images by file URL in `FileImageLoader`

`URL.asImageAndSize` (used by `FileImageLoader.loadFromCache`) previously
called `UIImage(contentsOfFile:)` / `NSImage(contentsOfFile:)` on every
access. A SwiftUI paywall with many `RemoteImage` instances (e.g. a looping
carousel that materialises multiple copies of each page) ends up hitting
this path hundreds of times because `StateObject(wrappedValue:)` evaluates
its closure on every host view re-init. UIKit/AppKit's internal caches
still cost ~0.5–1 ms per call, which adds up to ~100+ ms of main-thread
blocking for only a handful of unique URLs.

Introduce a process-wide `DecodedImageCache` backed by `NSCache<NSURL, _>`
so repeated lookups for the same URL become a dictionary hit. `NSCache`
evicts on memory pressure, so we don't pin images forever. Reads/writes
are serialised through a concurrent dispatch queue with a barrier on
writes to keep the cache thread-safe.

Made-with: Cursor

---------

Co-authored-by: Facundo Menzella <facumenzella@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Pol Miro <polmiro@gmail.com>
Co-authored-by: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
This was referenced Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants