Skip to content

Commit fd34007

Browse files
refactor(audience): enable nullable reference types on Slice 2 files
Applies #nullable enable + targeted `?` annotations per the HttpTransport precedent (PR 691). Scope: the six files introduced or substantially modified in Slice 2. - IEvent.cs: no annotations needed; impl returns non-null. - IdentityType.cs: ToLowercaseString return → string? (existing comment documents null-on-unknown-cast contract). - TypedEvents.cs: optional properties → string?; also tightens IsIso4217 to non-null param (Currency gets an explicit null-or-invalid check before dict assignment). Adds a Resource.Currency null/empty validation that was missing. - Core/ConsentStore.cs: no annotations needed. - Core/Constants.cs: BaseUrl/MessagesUrl/ConsentUrl/DataUrl params → string? (code already guards with `publishableKey != null`). - Events/MessageBuilder.cs: Track/Identify guarded params (anonymousId, userId, identityType) → string?; properties/traits → Dictionary<string, object>?. Truncate tightened to non-null input (callers guard before the call). Compile-time annotations only. Zero runtime behaviour change except Resource.Currency now throws on null/empty, matching the "Required" comment on the field.
1 parent e7f8188 commit fd34007

7 files changed

Lines changed: 63 additions & 29 deletions

File tree

src/Packages/Audience/Runtime/Core/ConsentStore.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#nullable enable
2+
13
using System;
24
using System.IO;
35

src/Packages/Audience/Runtime/Core/Constants.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#nullable enable
2+
13
namespace Immutable.Audience
24
{
35
internal static class Constants
@@ -23,11 +25,11 @@ internal static class Constants
2325

2426
internal const string PublishableKeyHeader = "x-immutable-publishable-key";
2527

26-
internal static string MessagesUrl(string publishableKey) => BaseUrl(publishableKey) + MessagesPath;
27-
internal static string ConsentUrl(string publishableKey) => BaseUrl(publishableKey) + ConsentPath;
28-
internal static string DataUrl(string publishableKey) => BaseUrl(publishableKey) + DataPath;
28+
internal static string MessagesUrl(string? publishableKey) => BaseUrl(publishableKey) + MessagesPath;
29+
internal static string ConsentUrl(string? publishableKey) => BaseUrl(publishableKey) + ConsentPath;
30+
internal static string DataUrl(string? publishableKey) => BaseUrl(publishableKey) + DataPath;
2931

30-
internal static string BaseUrl(string publishableKey) =>
32+
internal static string BaseUrl(string? publishableKey) =>
3133
publishableKey != null && publishableKey.StartsWith(TestKeyPrefix)
3234
? SandboxBaseUrl
3335
: ProductionBaseUrl;

src/Packages/Audience/Runtime/Core/Identity.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#nullable enable
2+
13
using System;
24
using System.IO;
35

@@ -12,13 +14,13 @@ namespace Immutable.Audience
1214
internal sealed class Identity
1315
{
1416
// In-memory cache — volatile so background threads always see the latest write.
15-
private static volatile string _cachedId;
17+
private static volatile string? _cachedId;
1618
private static readonly object _sync = new object();
1719

1820
// Returns the existing anonymous ID, or null if none exists. Never
1921
// creates one — for call sites that must not register a new ID as
2022
// a side-effect.
21-
internal static string Get(string persistentDataPath)
23+
internal static string? Get(string persistentDataPath)
2224
{
2325
if (_cachedId != null) return _cachedId;
2426

@@ -59,7 +61,7 @@ internal static void ClearCache()
5961
// Returns the anonymous ID, generating and persisting it on first call.
6062
// Returns null without touching disk when consent is None.
6163
// Safe to call from any thread after ImmutableAudience.Init() has run on the main thread.
62-
internal static string GetOrCreate(string persistentDataPath, ConsentLevel consent)
64+
internal static string? GetOrCreate(string persistentDataPath, ConsentLevel consent)
6365
{
6466
// No ID until the player grants at least anonymous consent.
6567
if (consent == ConsentLevel.None)

src/Packages/Audience/Runtime/Events/IEvent.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#nullable enable
2+
13
using System.Collections.Generic;
24

35
namespace Immutable.Audience

src/Packages/Audience/Runtime/Events/MessageBuilder.cs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#nullable enable
2+
13
using System;
24
using System.Collections.Generic;
35

@@ -7,10 +9,10 @@ internal static class MessageBuilder
79
{
810
internal static Dictionary<string, object> Track(
911
string eventName,
10-
string anonymousId,
11-
string userId,
12+
string? anonymousId,
13+
string? userId,
1214
string packageVersion,
13-
Dictionary<string, object> properties = null)
15+
Dictionary<string, object>? properties = null)
1416
{
1517
var msg = BuildBase(MessageTypes.Track, packageVersion);
1618
msg["eventName"] = Truncate(eventName, Constants.MaxFieldLength);
@@ -22,17 +24,20 @@ internal static Dictionary<string, object> Track(
2224
msg[MessageFields.UserId] = Truncate(userId, Constants.MaxFieldLength);
2325

2426
if (properties != null && properties.Count > 0)
27+
{
28+
TruncateStringValues(properties);
2529
msg["properties"] = properties;
30+
}
2631

2732
return msg;
2833
}
2934

3035
internal static Dictionary<string, object> Identify(
31-
string anonymousId,
32-
string userId,
33-
string identityType,
36+
string? anonymousId,
37+
string? userId,
38+
string? identityType,
3439
string packageVersion,
35-
Dictionary<string, object> traits = null)
40+
Dictionary<string, object>? traits = null)
3641
{
3742
var msg = BuildBase(MessageTypes.Identify, packageVersion);
3843

@@ -46,7 +51,10 @@ internal static Dictionary<string, object> Identify(
4651
msg["identityType"] = Truncate(identityType, Constants.MaxFieldLength);
4752

4853
if (traits != null && traits.Count > 0)
54+
{
55+
TruncateStringValues(traits);
4956
msg["traits"] = traits;
57+
}
5058

5159
return msg;
5260
}
@@ -84,9 +92,20 @@ private static Dictionary<string, object> BuildBase(string type, string packageV
8492

8593
private static string Truncate(string s, int maxLen)
8694
{
87-
if (s == null || s.Length <= maxLen)
95+
if (s.Length <= maxLen)
8896
return s;
8997
return s.Substring(0, maxLen);
9098
}
99+
100+
private static void TruncateStringValues(Dictionary<string, object> dict)
101+
{
102+
// Snapshot keys to avoid mutating the collection during iteration.
103+
var keys = new List<string>(dict.Keys);
104+
foreach (var key in keys)
105+
{
106+
if (dict[key] is string s && s.Length > Constants.MaxFieldLength)
107+
dict[key] = Truncate(s, Constants.MaxFieldLength);
108+
}
109+
}
91110
}
92111
}

src/Packages/Audience/Runtime/Events/TypedEvents.cs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#nullable enable
2+
13
using System;
24
using System.Collections.Generic;
35

@@ -31,9 +33,9 @@ public class Progression : IEvent
3133
// Required.
3234
public ProgressionStatus Status { get; set; }
3335
// Optional.
34-
public string World { get; set; }
35-
public string Level { get; set; }
36-
public string Stage { get; set; }
36+
public string? World { get; set; }
37+
public string? Level { get; set; }
38+
public string? Stage { get; set; }
3739
public int? Score { get; set; }
3840
public float? DurationSec { get; set; }
3941

@@ -81,16 +83,19 @@ public class Resource : IEvent
8183
{
8284
// Required.
8385
public ResourceFlow Flow { get; set; }
84-
public string Currency { get; set; }
86+
public string? Currency { get; set; }
8587
public float Amount { get; set; }
8688
// Optional.
87-
public string ItemType { get; set; }
88-
public string ItemId { get; set; }
89+
public string? ItemType { get; set; }
90+
public string? ItemId { get; set; }
8991

9092
public string EventName => "resource";
9193

9294
public Dictionary<string, object> ToProperties()
9395
{
96+
if (string.IsNullOrEmpty(Currency))
97+
throw new ArgumentException("Resource.Currency must not be null or empty");
98+
9499
var props = new Dictionary<string, object>
95100
{
96101
["flow"] = Flow.ToLowercaseString(),
@@ -109,21 +114,21 @@ public Dictionary<string, object> ToProperties()
109114
public class Purchase : IEvent
110115
{
111116
// Required. ISO 4217 three-letter uppercase currency code.
112-
public string Currency { get; set; }
117+
public string? Currency { get; set; }
113118
// Required.
114119
public decimal Value { get; set; }
115120
// Optional.
116-
public string ItemId { get; set; }
117-
public string ItemName { get; set; }
121+
public string? ItemId { get; set; }
122+
public string? ItemName { get; set; }
118123
public int? Quantity { get; set; }
119-
public string TransactionId { get; set; }
124+
public string? TransactionId { get; set; }
120125

121126
public string EventName => "purchase";
122127

123128
// Hand-rolled to avoid pulling System.Text.RegularExpressions into the IL2CPP build.
124129
private static bool IsIso4217(string s)
125130
{
126-
if (s == null || s.Length != 3) return false;
131+
if (s.Length != 3) return false;
127132
for (var i = 0; i < 3; i++)
128133
{
129134
var c = s[i];
@@ -134,7 +139,7 @@ private static bool IsIso4217(string s)
134139

135140
public Dictionary<string, object> ToProperties()
136141
{
137-
if (!IsIso4217(Currency))
142+
if (Currency == null || !IsIso4217(Currency))
138143
throw new ArgumentException(
139144
$"Purchase.Currency '{Currency}' must be a three-letter uppercase ISO 4217 code");
140145

@@ -157,7 +162,7 @@ public Dictionary<string, object> ToProperties()
157162
public class MilestoneReached : IEvent
158163
{
159164
// Required.
160-
public string Name { get; set; }
165+
public string? Name { get; set; }
161166

162167
public string EventName => "milestone_reached";
163168

src/Packages/Audience/Runtime/IdentityType.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#nullable enable
2+
13
namespace Immutable.Audience
24
{
35
// Identity provider accepted by the Audience backend.
@@ -18,7 +20,7 @@ internal static class IdentityTypeExtensions
1820
// Returns null on unknown casts. The string overloads of Identify /
1921
// Alias check for null/empty and drop + warn, so an out-of-range
2022
// cast surfaces as a dropped event, not a corrupt wire payload.
21-
internal static string ToLowercaseString(this IdentityType type) => type switch
23+
internal static string? ToLowercaseString(this IdentityType type) => type switch
2224
{
2325
IdentityType.Passport => "passport",
2426
IdentityType.Steam => "steam",

0 commit comments

Comments
 (0)