feat(audience): enforce required properties on Track(IEvent) predefined events (SDK-225)#697
Merged
ImmutableJeffrey merged 1 commit intoApr 22, 2026
Conversation
…ed events
Attribution and conversion pipelines depend on predefined events
carrying their required fields. The C# compiler enforces field types
but does not enforce that required fields are set to a non-default
value, so a caller like `Track(new Purchase { Currency = "USD" })`
was silently shipping a zero-value purchase and breaking downstream
reporting.
Make each required value-typed field nullable on the typed IEvent
implementations so an unset caller is distinguishable from a
zero / default value, and throw a clear `ArgumentException` from
`ToProperties()` when a required field is null. The existing
try/catch inside `ImmutableAudience.Track(IEvent)` catches the
throw, logs a warning naming the event and the missing field, and
drops the event — consistent with the never-crash convention on the
game thread.
- Progression.Status: ProgressionStatus -> ProgressionStatus?
- Resource.Flow: ResourceFlow -> ResourceFlow?
- Resource.Amount: float -> float?
- Purchase.Value: decimal -> decimal?
Track(string) is explicitly out of scope for runtime validation —
callers using the string overload opt out of the typed path. Tighten
the existing doc comment with a concrete example (purchase without
currency / value) so the gap is unambiguous.
Tests: each predefined event gets a missing-required-field case
asserting the throw; an integration test on Track(IEvent) confirms
the throw is caught, logged, and the event dropped rather than
shipping a malformed payload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nattb8
approved these changes
Apr 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Progression.Status,Resource.Flow,Resource.Amount,Purchase.Value— so an unset caller is distinguishable from the enum / zero default.ToProperties()throwsArgumentExceptionnaming the missing field when a required property is null. The existing try/catch inImmutableAudience.Track(IEvent)catches, logs, and drops the event — consistent with the never-crash convention on the game thread.Resource.Currency,Purchase.Currencyvia ISO 4217,MilestoneReached.Name) stay as-is — no behavior change there.Track(string)doc comment with a concrete example (purchase without currency / value) so the attribution / conversion reporting risk is explicit, per Natalie's follow-up on the merged PR.Track(IEvent)confirming a malformed Purchase is caught, logged, and dropped (161 → 168).Rationale
Attribution and conversion pipelines depend on predefined events carrying their required fields. The C# compiler enforces field types but does not enforce that required fields are set to a non-default value, so
Track(new Purchase { Currency = "USD" })was silently shipping a zero-value purchase and breaking downstream reporting.Web SDK sidesteps this with TS conditional types at compile time. Unity C# cannot — required fields must be enforced at runtime instead. Natalie's explicit direction on Slack: "for Unity maybe we can only call out in the docs & comments for
Track(string...)and enforce it inTrack(IEvent)."Field mapping matches the canonical schema in Unity SDK Event Reference v1.
Breaking change
Callers that read a required property directly — e.g.
decimal v = purchase.Value;— needpurchase.Value!.Valueafter this change. Construction patterns likenew Purchase { Value = 9.99m }are unaffected because of implicit nullable conversion.Out of scope
Track(string)with a predefined event name — docs-only per Natalie's direction.Linear: SDK-225
Sister ticket: SDK-222 (PR #696)
Parent: SDK-99 Unity SDK v1