Skip to content

feat(audience): validate required properties for reserved events at runtime#823

Merged
nattb8 merged 1 commit into
mainfrom
feat/sdk-687-reserved-event-validation
Jul 23, 2026
Merged

feat(audience): validate required properties for reserved events at runtime#823
nattb8 merged 1 commit into
mainfrom
feat/sdk-687-reserved-event-validation

Conversation

@nattb8

@nattb8 nattb8 commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Ticket: SDK-687

  • Track(string, Dictionary) now validates required properties for purchase, progression, resource, achievement_unlocked, and milestone_reached, throwing ArgumentException if any are missing — previously it silently accepted these reserved events with no properties at all, bypassing every check the typed classes enforce.
  • New internal IBuiltInEvent marker interface lets Track(IEvent) tell Immutable's own typed classes apart from a consumer's custom IEvent. Built-in events now throw straight through on a validation failure (matching Identify()/Alias()'s existing behavior for caller mistakes); a consumer's own custom IEvent is still caught and dropped with a warning, since a third-party bug shouldn't crash the game.
  • New test reflects over every IBuiltInEvent implementation to catch a future one being added without a matching ReservedEvents.RequiredProperties entry.
  • Purchase.Value changes from decimal to string, to avoid precision loss on very large or high-precision amounts.
Before After
Track("purchase", new Dictionary<string,object>()) Ships silently with missing fields Throws, listing exactly which required properties are missing
Track(new Purchase{Currency="USD"}) (missing Value) Caught by Track(IEvent) and warn-dropped Throws straight through, matching Identify()/Alias()
Track(new SomeThirdPartyCustomEvent()) whose ToProperties() throws Caught and warn-dropped Unchanged, still caught and warn-dropped
Purchase.Value decimal string

Breaking changes to flag before release: existing code setting Purchase.Value with a decimal literal (e.g. 9.99m) will fail to compile. Existing code with a latent bug that sends an incomplete built-in typed event will now throw instead of silently warning — worth a major version bump and changelog callout, not a silent release.

Test plan

  • dotnet build, dotnet test pass: 394 passed, 1 skipped (pre-existing), 0 failed
  • dotnet format --verify-no-changes clean
  • New/updated tests cover: string-overload missing required props throws per reserved event (including milestone_reached), all-required-props-present does not throw, unrecognised event names still unvalidated, a built-in typed event (Purchase) missing a required field now throws instead of warn-dropping, a genuinely custom IEvent whose ToProperties() throws is still caught and warn-dropped, every IBuiltInEvent implementation has a ReservedEvents entry

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown

Audience SDK — Build Size

Platform SDK Size Change
Android 0.40 / 20.00 MB +0.04 MB
Windows 0.17 / 20.00 MB +0.01 MB
iOS 8.61 / 20.00 MB +0.09 MB
macOS 1.00 / 20.00 MB +0.10 MB

SDK Size = build minus empty app. Change = vs baseline. Fails if any platform exceeds its absolute size limit.

@nattb8
nattb8 marked this pull request as ready for review July 23, 2026 06:15
@nattb8
nattb8 requested review from a team as code owners July 23, 2026 06:15
@nattb8
nattb8 force-pushed the feat/sdk-687-reserved-event-validation branch from 6dfb6de to 5025759 Compare July 23, 2026 06:41
…untime

Track(string, Dictionary) let a caller send purchase/progression/resource/
achievement_unlocked/milestone_reached with no properties at all, bypassing
every check the typed IEvent classes enforce. It now validates the same
required properties those classes do, throwing ArgumentException the same
way an empty event name already does in that method.

Also fixes an inconsistency this change would otherwise have made worse:
Track(IEvent) caught every ToProperties() exception and warned instead of
throwing, including for Immutable's own built-in events. That catch exists
to stop a buggy third-party IEvent from crashing the game, but a caller
forgetting a required field on Purchase/Progression/etc is a caller bug like
any other, and should throw the same way Identify()/Alias() already do for
equivalent mistakes. A new internal IBuiltInEvent marker lets Track(IEvent)
tell the two cases apart: built-in events now throw straight through, a
consumer's own custom IEvent implementation is still caught and dropped
with a warning. A new test reflects over every IBuiltInEvent implementation
to guard against a future one being added without a matching
ReservedEvents.RequiredProperties entry.

Purchase.Value changes from decimal to string: a decimal can't precisely
hold very large or high-precision amounts, and this keeps it consistent
with the TS SDK's equivalent change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@nattb8
nattb8 force-pushed the feat/sdk-687-reserved-event-validation branch from 5025759 to 29d6623 Compare July 23, 2026 08:44
@nattb8
nattb8 merged commit 44da6db into main Jul 23, 2026
49 of 56 checks passed
@nattb8
nattb8 deleted the feat/sdk-687-reserved-event-validation branch July 23, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants