public void VariationDetailSendsFeatureEventWithReasonForValidFlag() { var flag = new FeatureFlagBuilder().Value(LdValue.Of("a")).Variation(1).Version(1000) .TrackEvents(true).DebugEventsUntilDate(UnixMillisecondTime.OfMillis(2000)) .Reason(EvaluationReason.OffReason).Build(); _testData.Update(_testData.Flag("flag").PreconfiguredFlag(flag)); using (LdClient client = MakeClient(user)) { EvaluationDetail <string> result = client.StringVariationDetail("flag", "b"); Assert.Equal("a", result.Value); Assert.Equal(EvaluationReason.OffReason, result.Reason); Assert.Collection(eventProcessor.Events, e => CheckIdentifyEvent(e, user), e => { EvaluationEvent fe = Assert.IsType <EvaluationEvent>(e); Assert.Equal("flag", fe.FlagKey); Assert.Equal("a", fe.Value.AsString); Assert.Equal(1, fe.Variation); Assert.Equal(1000, fe.FlagVersion); Assert.Equal("b", fe.Default.AsString); Assert.True(fe.TrackEvents); Assert.Equal(UnixMillisecondTime.OfMillis(2000), fe.DebugEventsUntilDate); Assert.Equal(EvaluationReason.OffReason, fe.Reason); Assert.NotEqual(0, fe.Timestamp.Value); }); } }
/// <summary> /// Parses the user index from a JSON representation. If the JSON string is null or /// empty, it returns an empty user index. /// </summary> /// <param name="json">the JSON representation</param> /// <returns>the parsed data</returns> /// <exception cref="FormatException">if the JSON is malformed</exception> public static UserIndex Deserialize(string json) { if (string.IsNullOrEmpty(json)) { return(new UserIndex()); } var builder = ImmutableList.CreateBuilder <IndexEntry>(); try { var r = JReader.FromString(json); for (var ar0 = r.Array(); ar0.Next(ref r);) { var ar1 = r.Array(); if (ar1.Next(ref r)) { var userId = r.String(); if (ar1.Next(ref r)) { var timeMillis = r.Long(); builder.Add(new IndexEntry { UserId = userId, Timestamp = UnixMillisecondTime.OfMillis(timeMillis) }); ar1.Next(ref r); } } } } catch (Exception e) { throw new FormatException("invalid stored user index", e); } return(new UserIndex(builder.ToImmutable())); }
public void SummarizeEventIncrementsCounters() { var time = UnixMillisecondTime.OfMillis(1000); string flag1Key = "flag1", flag2Key = "flag2", unknownFlagKey = "badkey"; int flag1Version = 100, flag2Version = 200; int variation1 = 1, variation2 = 2; LdValue value1 = LdValue.Of("value1"), value2 = LdValue.Of("value2"), value99 = LdValue.Of("value99"), default1 = LdValue.Of("default1"), default2 = LdValue.Of("default2"), default3 = LdValue.Of("default3"); EventSummarizer es = new EventSummarizer(); es.SummarizeEvent(time, flag1Key, flag1Version, variation1, value1, default1); es.SummarizeEvent(time, flag1Key, flag1Version, variation2, value2, default1); es.SummarizeEvent(time, flag2Key, flag2Version, variation1, value99, default2); es.SummarizeEvent(time, flag1Key, flag1Version, variation1, value1, default1); es.SummarizeEvent(time, unknownFlagKey, null, null, default3, default3); EventSummary data = es.Snapshot(); Dictionary <EventsCounterKey, EventsCounterValue> expected = new Dictionary <EventsCounterKey, EventsCounterValue>(); Assert.Equal(new EventsCounterValue(2, value1, default1), data.Counters[new EventsCounterKey(flag1Key, flag1Version, variation1)]); Assert.Equal(new EventsCounterValue(1, value2, default1), data.Counters[new EventsCounterKey(flag1Key, flag1Version, variation2)]); Assert.Equal(new EventsCounterValue(1, value99, default2), data.Counters[new EventsCounterKey(flag2Key, flag2Version, variation1)]); Assert.Equal(new EventsCounterValue(1, default3, default3), data.Counters[new EventsCounterKey(unknownFlagKey, null, null)]); }
public void AllFlagsStateReturnsStateWithReasons() { var flag1 = new FeatureFlagBuilder("key1").Version(100) .OffVariation(0).Variations(LdValue.Of("value1")) .Build(); var flag2 = new FeatureFlagBuilder("key2").Version(200) .OffVariation(1).Variations(LdValue.Of("x"), LdValue.Of("value2")) .TrackEvents(true).DebugEventsUntilDate(UnixMillisecondTime.OfMillis(1000)) .Build(); testData.UsePreconfiguredFlag(flag1); testData.UsePreconfiguredFlag(flag2); var state = client.AllFlagsState(user, FlagsStateOption.WithReasons); Assert.True(state.Valid); var expectedString = @"{""key1"":""value1"",""key2"":""value2"", ""$flagsState"":{ ""key1"":{ ""variation"":0,""version"":100,""reason"":{""kind"":""OFF""} },""key2"":{ ""variation"":1,""version"":200,""reason"":{""kind"":""OFF""},""trackEvents"":true,""debugEventsUntilDate"":1000 } }, ""$valid"":true }"; var actualString = LdJsonSerialization.SerializeObject(state); AssertHelpers.JsonEqual(expectedString, actualString); }
public void SerializeFlagWithAllProperties() { var flag1 = new FeatureFlagBuilder() .Version(1) .Value(LdValue.Of(false)) .Variation(2) .FlagVersion(3) .Reason(EvaluationReason.OffReason) .TrackEvents(true) .DebugEventsUntilDate(UnixMillisecondTime.OfMillis(1234)) .Build(); AssertJsonEqual(@"{""version"":1,""value"":false,""variation"":2,""flagVersion"":3," + @"""reason"":{""kind"":""OFF""},""trackEvents"":true,""debugEventsUntilDate"":1234}", DataModelSerialization.SerializeFlag(flag1)); // make sure we're treating trackReason separately from trackEvents var flag2 = new FeatureFlagBuilder() .Version(1) .Value(LdValue.Of(false)) .Reason(EvaluationReason.OffReason) .Variation(2) .FlagVersion(3) .TrackReason(true) .Build(); AssertJsonEqual(@"{""version"":1,""value"":false,""variation"":2,""flagVersion"":3," + @"""reason"":{""kind"":""OFF""},""trackReason"":true}", DataModelSerialization.SerializeFlag(flag2)); }
public object ReadJson(ref JReader reader) { var valid = true; var flags = new Dictionary <string, FlagState>(); for (var topLevelObj = reader.Object(); topLevelObj.Next(ref reader);) { var key = topLevelObj.Name.ToString(); switch (key) { case "$valid": valid = reader.Bool(); break; case "$flagsState": for (var flagsObj = reader.Object(); flagsObj.Next(ref reader);) { var subKey = flagsObj.Name.ToString(); var flag = flags.ContainsKey(subKey) ? flags[subKey] : new FlagState(); for (var metaObj = reader.Object(); metaObj.Next(ref reader);) { switch (metaObj.Name.ToString()) { case "variation": flag.Variation = reader.IntOrNull(); break; case "version": flag.Version = reader.IntOrNull(); break; case "trackEvents": flag.TrackEvents = reader.Bool(); break; case "debugEventsUntilDate": var n = reader.LongOrNull(); flag.DebugEventsUntilDate = n.HasValue ? UnixMillisecondTime.OfMillis(n.Value) : (UnixMillisecondTime?)null; break; case "reason": flag.Reason = EvaluationReasonConverter.ReadJsonNullableValue(ref reader); break; } } flags[subKey] = flag; } break; default: var flagForValue = flags.ContainsKey(key) ? flags[key] : new FlagState(); flagForValue.Value = LdValueConverter.ReadJsonValue(ref reader); flags[key] = flagForValue; break; } } return(new FeatureFlagsState(valid, flags)); }
public void SetIndex() { var index = new UserIndex().UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)); _wrapper.SetIndex(index); var serializedData = _persistentStore.GetValue(ExpectedEnvironmentNamespace, ExpectedIndexKey); AssertJsonEqual(index.Serialize(), serializedData); }
public void Serialize() { var ui = new UserIndex() .UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)) .UpdateTimestamp("user2", UnixMillisecondTime.OfMillis(2000)); var json = ui.Serialize(); var expected = @"[[""user1"",1000],[""user2"",2000]]"; AssertJsonEqual(expected, json); }
public void UpdateTimestampForExistingUser() { var ui = new UserIndex() .UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)) .UpdateTimestamp("user2", UnixMillisecondTime.OfMillis(2000)); ui = ui.UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(2001)); Assert.Collection(ui.Data, AssertEntry("user2", 2000), AssertEntry("user1", 2001)); }
public void PruneWhenLimitIsNotExceeded() { var ui = new UserIndex() .UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)) .UpdateTimestamp("user2", UnixMillisecondTime.OfMillis(2000)); Assert.Same(ui, ui.Prune(3, out var removed1)); Assert.Empty(removed1); Assert.Same(ui, ui.Prune(2, out var removed2)); Assert.Empty(removed2); }
public void SummaryEventIsSerialized() { var summary = new EventSummary(); summary.NoteTimestamp(UnixMillisecondTime.OfMillis(1001)); summary.IncrementCounter("first", 1, 11, LdValue.Of("value1a"), LdValue.Of("default1")); summary.IncrementCounter("second", 1, 21, LdValue.Of("value2a"), LdValue.Of("default2")); summary.IncrementCounter("first", 1, 11, LdValue.Of("value1a"), LdValue.Of("default1")); summary.IncrementCounter("first", 1, 12, LdValue.Of("value1a"), LdValue.Of("default1")); summary.IncrementCounter("second", 2, 21, LdValue.Of("value2b"), LdValue.Of("default2")); summary.IncrementCounter("second", null, 21, LdValue.Of("default2"), LdValue.Of("default2")); // flag exists (has version), but eval failed (no variation) summary.IncrementCounter("third", null, null, LdValue.Of("default3"), LdValue.Of("default3")); // flag doesn't exist (no version) summary.NoteTimestamp(UnixMillisecondTime.OfMillis(1000)); summary.NoteTimestamp(UnixMillisecondTime.OfMillis(1002)); var f = new EventOutputFormatter(new EventsConfiguration()); var outputEvent = LdValue.Parse(f.SerializeOutputEvents(new object[0], summary, out var count)).Get(0); Assert.Equal(1, count); Assert.Equal("summary", outputEvent.Get("kind").AsString); Assert.Equal(1000, outputEvent.Get("startDate").AsInt); Assert.Equal(1002, outputEvent.Get("endDate").AsInt); var featuresJson = outputEvent.Get("features"); Assert.Equal(3, featuresJson.Count); var firstJson = featuresJson.Get("first"); Assert.Equal("default1", firstJson.Get("default").AsString); TestUtil.AssertContainsInAnyOrder(firstJson.Get("counters").AsList(LdValue.Convert.Json), LdValue.Parse(@"{""value"":""value1a"",""variation"":1,""version"":11,""count"":2}"), LdValue.Parse(@"{""value"":""value1a"",""variation"":1,""version"":12,""count"":1}")); var secondJson = featuresJson.Get("second"); Assert.Equal("default2", secondJson.Get("default").AsString); TestUtil.AssertContainsInAnyOrder(secondJson.Get("counters").AsList(LdValue.Convert.Json), LdValue.Parse(@"{""value"":""value2a"",""variation"":1,""version"":21,""count"":1}"), LdValue.Parse(@"{""value"":""value2b"",""variation"":2,""version"":21,""count"":1}"), LdValue.Parse(@"{""value"":""default2"",""version"":21,""count"":1}")); var thirdJson = featuresJson.Get("third"); Assert.Equal("default3", thirdJson.Get("default").AsString); TestUtil.AssertContainsInAnyOrder(thirdJson.Get("counters").AsList(LdValue.Convert.Json), LdValue.Parse(@"{""unknown"":true,""value"":""default3"",""count"":1}")); }
public void SummarizeEventSetsStartAndEndDates() { var time1 = UnixMillisecondTime.OfMillis(1000); var time2 = UnixMillisecondTime.OfMillis(2000); var time3 = UnixMillisecondTime.OfMillis(3000); EventSummarizer es = new EventSummarizer(); es.SummarizeEvent(time2, "flag", null, null, LdValue.Null, LdValue.Null); es.SummarizeEvent(time1, "flag", null, null, LdValue.Null, LdValue.Null); es.SummarizeEvent(time3, "flag", null, null, LdValue.Null, LdValue.Null); EventSummary data = es.Snapshot(); Assert.Equal(time1, data.StartDate); Assert.Equal(time3, data.EndDate); }
public void PruneRemovesLeastRecentUsers() { var ui = new UserIndex() .UpdateTimestamp("user1", UnixMillisecondTime.OfMillis(1000)) .UpdateTimestamp("user2", UnixMillisecondTime.OfMillis(2000)) .UpdateTimestamp("user3", UnixMillisecondTime.OfMillis(1111)) // deliberately out of order .UpdateTimestamp("user4", UnixMillisecondTime.OfMillis(3000)) .UpdateTimestamp("user5", UnixMillisecondTime.OfMillis(4000)); var ui1 = ui.Prune(3, out var removed); Assert.Equal(ImmutableList.Create("user1", "user3"), removed); Assert.Collection(ui1.Data, AssertEntry("user2", 2000), AssertEntry("user4", 3000), AssertEntry("user5", 4000)); }
internal static DateTime?ValueToDate(LdValue value) { if (value.IsString) { try { return(DateTime.Parse(value.AsString).ToUniversalTime()); } catch (FormatException) { return(null); } } if (value.IsNumber) { return(UnixMillisecondTime.OfMillis(value.AsLong).AsDateTime); } return(null); }
public void UsePreconfiguredFlag() { CreateAndStart(); _updates.ExpectInit(_initialUser); var flag = new FeatureFlagBuilder().Version(1).Value(true).Variation(0).Reason(EvaluationReason.OffReason) .TrackEvents(true).TrackReason(true).DebugEventsUntilDate(UnixMillisecondTime.OfMillis(123)).Build(); _td.Update(_td.Flag("flag1").PreconfiguredFlag(flag)); var item1 = _updates.ExpectUpsert(_initialUser, "flag1"); Assert.Equal(flag, item1.Item); _td.Update(_td.Flag("flag1").PreconfiguredFlag(flag)); var item2 = _updates.ExpectUpsert(_initialUser, "flag1"); var updatedFlag = new FeatureFlagBuilder(flag).Version(2).Build(); Assert.Equal(updatedFlag, item2.Item); }
public async Task <StoreMetadata?> GetMetadataAsync() { var db = _redis.GetDatabase(); var value = await db.StringGetAsync(_syncTimeKey); if (value.IsNull) { return(null); } if (value == "") { return(new StoreMetadata { LastUpToDate = null }); } var millis = long.Parse(value); return(new StoreMetadata { LastUpToDate = UnixMillisecondTime.OfMillis(millis) }); }
public async Task <StoreMetadata?> GetMetadataAsync() { var key = PrefixedNamespace(MetadataKey); var request = new GetItemRequest(_tableName, MakeKeysMap(key, key), true); var result = await _client.GetItemAsync(request); if (result.Item is null || result.Item.Count == 0) { return(null); } if (!result.Item.TryGetValue(SyncTimeAttr, out var syncTimeValue) || string.IsNullOrEmpty(syncTimeValue.N)) { return(new StoreMetadata { LastUpToDate = null }); } if (!long.TryParse(syncTimeValue.N, out var milliseconds)) { throw new InvalidOperationException("Invalid data in DynamoDB: non-numeric timestamp"); } return(new StoreMetadata { LastUpToDate = UnixMillisecondTime.OfMillis(milliseconds) }); }
public void CanDeserializeFromJson() { var state = FeatureFlagsState.Builder(FlagsStateOption.WithReasons) .AddFlag("key1", LdValue.Of("value1"), 0, EvaluationReason.OffReason, 100, false, null) .AddFlag("key2", LdValue.Of("value2"), 1, EvaluationReason.FallthroughReason, 200, true, UnixMillisecondTime.OfMillis(1000)) .Build(); var jsonString = LdJsonSerialization.SerializeObject(state); var state1 = LdJsonSerialization.DeserializeObject <FeatureFlagsState>(jsonString); Assert.Equal(state, state1); }
private void AssertFlagHasAllProperties(FeatureFlag flag) { Assert.Equal("flag-key", flag.Key); Assert.Equal(99, flag.Version); Assert.True(flag.On); Assert.Equal("123", flag.Salt); Assert.Collection(flag.Prerequisites, p => { Assert.Equal("prereqkey", p.Key); Assert.Equal(3, p.Variation); }); Assert.Collection(flag.Targets, t => { Assert.Equal(1, t.Variation); Assert.Equal(ImmutableList.Create("key1", "key2"), t.Values); }); Assert.Collection(flag.Rules, r => { Assert.Equal("id0", r.Id); Assert.True(r.TrackEvents); Assert.Equal(2, r.Variation); Assert.Null(r.Rollout); Assert.Collection(r.Clauses, c => { Assert.Equal(UserAttribute.Name, c.Attribute); Assert.Equal(Operator.In, c.Op); Assert.Equal(ImmutableList.Create(LdValue.Of("Lucy"), LdValue.Of("Mina")), c.Values); Assert.True(c.Negate); }); }, r => { Assert.Equal("id1", r.Id); Assert.False(r.TrackEvents); Assert.Null(r.Variation); Assert.NotNull(r.Rollout); Assert.Collection(r.Rollout.Value.Variations, v => { Assert.Equal(2, v.Variation); Assert.Equal(40000, v.Weight); Assert.False(v.Untracked); }, v => { Assert.Equal(1, v.Variation); Assert.Equal(60000, v.Weight); Assert.True(v.Untracked); }); Assert.Equal(UserAttribute.Email, r.Rollout.Value.BucketBy); Assert.Equal(RolloutKind.Experiment, r.Rollout.Value.Kind); Assert.Equal(123, r.Rollout.Value.Seed); Assert.Empty(r.Clauses); }); Assert.Equal(1, flag.Fallthrough.Variation); Assert.Null(flag.Fallthrough.Rollout); Assert.Equal(2, flag.OffVariation); Assert.Equal(ImmutableList.Create(LdValue.Of("a"), LdValue.Of("b"), LdValue.Of("c")), flag.Variations); Assert.True(flag.ClientSide); Assert.True(flag.TrackEvents); Assert.True(flag.TrackEventsFallthrough); Assert.Equal(UnixMillisecondTime.OfMillis(1000), flag.DebugEventsUntilDate); }
public static FeatureFlag ReadJsonValue(ref JReader reader) { LdValue value = LdValue.Null; int version = 0; int? flagVersion = null; int? variation = null; EvaluationReason? reason = null; bool trackEvents = false; bool trackReason = false; UnixMillisecondTime?debugEventsUntilDate = null; for (var or = reader.Object(); or.Next(ref reader);) { // The use of multiple == tests instead of switch allows for a slight optimization on // some platforms where it wouldn't always need to allocate a string for or.Name. See: // https://github.com/launchdarkly/dotnet-jsonstream/blob/master/src/LaunchDarkly.JsonStream/PropertyNameToken.cs var name = or.Name; if (name == "value") { value = LdJsonConverters.LdValueConverter.ReadJsonValue(ref reader); } else if (name == "version") { version = reader.Int(); } else if (name == "flagVersion") { flagVersion = reader.IntOrNull(); } else if (name == "variation") { variation = reader.IntOrNull(); } else if (name == "reason") { reason = LdJsonConverters.EvaluationReasonConverter.ReadJsonNullableValue(ref reader); } else if (name == "trackEvents") { trackEvents = reader.Bool(); } else if (name == "trackReason") { trackReason = reader.Bool(); } else if (name == "debugEventsUntilDate") { var dt = reader.LongOrNull(); if (dt.HasValue) { debugEventsUntilDate = UnixMillisecondTime.OfMillis(dt.Value); } } } return(new FeatureFlag( value, variation, reason, version, flagVersion, trackEvents, trackReason, debugEventsUntilDate )); }
public void NonTrackedEventsAreSummarized() { var mockSender = MakeMockSender(); var captured = EventCapture.From(mockSender); using (var ep = MakeProcessor(_config, mockSender)) { var flag1 = new TestFlagProperties { Key = "flagkey1", Version = 11 }; var flag2 = new TestFlagProperties { Key = "flagkey2", Version = 22 }; var value1 = LdValue.Of("value1"); var value2 = LdValue.Of("value2"); var default1 = LdValue.Of("default1"); var default2 = LdValue.Of("default2"); var earliestTime = UnixMillisecondTime.OfMillis(10000); var latestTime = UnixMillisecondTime.OfMillis(20000); RecordEval(ep, flag1, new TestEvalProperties { Timestamp = earliestTime, User = _user, Variation = 1, Value = value1, DefaultValue = default1 }); RecordEval(ep, flag1, new TestEvalProperties { Timestamp = UnixMillisecondTime.OfMillis(earliestTime.Value + 10), User = _user, Variation = 1, Value = value1, DefaultValue = default1 }); RecordEval(ep, flag1, new TestEvalProperties { Timestamp = UnixMillisecondTime.OfMillis(earliestTime.Value + 20), User = _user, Variation = 2, Value = value2, DefaultValue = default1 }); RecordEval(ep, flag2, new TestEvalProperties { Timestamp = latestTime, User = _user, Variation = 2, Value = value2, DefaultValue = default2 }); ep.Flush(); ep.WaitUntilInactive(); Assert.Collection(captured.Events, item => CheckIndexEvent(item, earliestTime, _userJson), item => CheckSummaryEventDetails(item, earliestTime, latestTime, MustHaveFlagSummary(flag1.Key, default1, MustHaveFlagSummaryCounter(value1, 1, flag1.Version, 2), MustHaveFlagSummaryCounter(value2, 2, flag1.Version, 1) ), MustHaveFlagSummary(flag2.Key, default2, MustHaveFlagSummaryCounter(value2, 2, flag2.Version, 1) ) ) ); } }
private Action <UserIndex.IndexEntry> AssertEntry(string id, int millis) => e => { Assert.Equal(id, e.UserId); Assert.Equal(UnixMillisecondTime.OfMillis(millis), e.Timestamp); };
public void CanSerializeToJson() { var state = FeatureFlagsState.Builder(FlagsStateOption.WithReasons) .AddFlag("key1", LdValue.Of("value1"), 0, EvaluationReason.OffReason, 100, false, null) .AddFlag("key2", LdValue.Of("value2"), 1, EvaluationReason.FallthroughReason, 200, true, UnixMillisecondTime.OfMillis(1000)) .Build(); var expectedString = @"{""key1"":""value1"",""key2"":""value2"", ""$flagsState"":{ ""key1"":{ ""variation"":0,""version"":100,""reason"":{""kind"":""OFF""} },""key2"":{ ""variation"":1,""version"":200,""reason"":{""kind"":""FALLTHROUGH""},""trackEvents"":true,""debugEventsUntilDate"":1000 } }, ""$valid"":true }"; var actualString = LdJsonSerialization.SerializeObject(state); AssertHelpers.JsonEqual(expectedString, actualString); }
public object ReadJson(ref JReader reader) { string key = null; int version = 0; bool deleted = false; bool on = false; ImmutableList <Prerequisite> prerequisites = null; ImmutableList <Target> targets = null; ImmutableList <FlagRule> rules = null; string salt = null; VariationOrRollout fallthrough = new VariationOrRollout(); int?offVariation = null; ImmutableList <LdValue> variations = null; bool trackEvents = false, trackEventsFallthrough = false; UnixMillisecondTime?debugEventsUntilDate = null; bool clientSide = false; for (var obj = reader.Object().WithRequiredProperties(_requiredProperties); obj.Next(ref reader);) { switch (obj.Name) { case var n when n == "key": key = reader.String(); break; case var n when n == "version": version = reader.Int(); break; case var n when n == "deleted": deleted = reader.Bool(); break; case var n when n == "on": on = reader.Bool(); break; case var n when n == "prerequisites": var prereqsBuilder = ImmutableList.CreateBuilder <Prerequisite>(); for (var arr = reader.ArrayOrNull(); arr.Next(ref reader);) { prereqsBuilder.Add(ReadPrerequisite(ref reader)); } prerequisites = prereqsBuilder.ToImmutable(); break; case var n when n == "targets": var targetsBuilder = ImmutableList.CreateBuilder <Target>(); for (var arr = reader.ArrayOrNull(); arr.Next(ref reader);) { targetsBuilder.Add(ReadTarget(ref reader)); } targets = targetsBuilder.ToImmutable(); break; case var n when n == "rules": var rulesBuilder = ImmutableList.CreateBuilder <FlagRule>(); for (var arr = reader.ArrayOrNull(); arr.Next(ref reader);) { rulesBuilder.Add(ReadFlagRule(ref reader)); } rules = rulesBuilder.ToImmutable(); break; case var n when n == "fallthrough": fallthrough = ReadVariationOrRollout(ref reader); break; case var n when n == "offVariation": offVariation = reader.IntOrNull(); break; case var n when n == "variations": variations = SerializationHelpers.ReadValues(ref reader); break; case var n when n == "salt": salt = reader.StringOrNull(); break; case var n when n == "trackEvents": trackEvents = reader.Bool(); break; case var n when n == "trackEventsFallthrough": trackEventsFallthrough = reader.Bool(); break; case var n when n == "debugEventsUntilDate": var dt = reader.LongOrNull(); debugEventsUntilDate = dt.HasValue ? UnixMillisecondTime.OfMillis(dt.Value) : (UnixMillisecondTime?)null; break; case var n when n == "clientSide": clientSide = reader.Bool(); break; } } if (key is null && !deleted) { throw new RequiredPropertyException("key", 0); } return(new FeatureFlag(key, version, deleted, on, prerequisites, targets, rules, fallthrough, offVariation, variations, salt, trackEvents, trackEventsFallthrough, debugEventsUntilDate, clientSide)); }
public object ReadJson(ref JReader reader) { var maybeValue = reader.LongOrNull(); return(maybeValue is null ? (UnixMillisecondTime?)null : UnixMillisecondTime.OfMillis(maybeValue.Value)); }