Skip to content

Commit 8f758fc

Browse files
feat: add variation Id to evaluation detail (#23)
* add new property'ValueId' to EvalDetail * some refactoring * fixed bug * more asserts * revert csproj file encoding change * fixed typo * nit * fixed xml comment --------- Co-authored-by: deleteLater <mikcczhang@gmail.com>
1 parent c77c96e commit 8f758fc

File tree

8 files changed

+67
-51
lines changed

8 files changed

+67
-51
lines changed

src/FeatBit.ServerSdk/Evaluation/EvalDetail.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,26 @@ public class EvalDetail<TValue>
2323
/// </summary>
2424
public TValue Value { get; set; }
2525

26+
/// <summary>
27+
/// The id of the flag value that was returned.
28+
/// </summary>
29+
public string ValueId { get; set; }
30+
2631
/// <summary>
2732
/// Constructs a new EvalDetail instance.
2833
/// </summary>
2934
/// <param name="key">the flag key</param>
3035
/// <param name="kind">the reason kind</param>
3136
/// <param name="reason">the evaluation reason</param>
3237
/// <param name="value">the flag value</param>
33-
public EvalDetail(string key, ReasonKind kind, string reason, TValue value)
38+
/// <param name="valueId">the id of flag value</param>
39+
public EvalDetail(string key, ReasonKind kind, string reason, TValue value, string valueId)
3440
{
3541
Key = key;
3642
Kind = kind;
3743
Reason = reason;
3844
Value = value;
45+
ValueId = valueId;
3946
}
4047
}
4148
}

src/FeatBit.ServerSdk/Evaluation/EvalResult.cs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using FeatBit.Sdk.Server.Model;
2+
13
namespace FeatBit.Sdk.Server.Evaluation
24
{
35
internal class EvalResult
@@ -6,42 +8,45 @@ internal class EvalResult
68

79
public string Reason { get; set; }
810

9-
public string Value { get; set; }
11+
public Variation Variation { get; set; }
1012

11-
private EvalResult(ReasonKind kind, string reason, string value)
13+
private EvalResult(ReasonKind kind, string reason, Variation variation)
1214
{
1315
Kind = kind;
1416
Reason = reason;
15-
Value = value;
17+
Variation = variation;
1618
}
1719

1820
// Indicates that the caller provided a flag key that did not match any known flag.
19-
public static readonly EvalResult FlagNotFound =
20-
new EvalResult(ReasonKind.Error, "flag not found", string.Empty);
21+
public static readonly EvalResult FlagNotFound = new(ReasonKind.Error, "flag not found", Variation.Empty);
2122

2223
// Indicates that there was an internal inconsistency in the flag data, e.g. a rule specified a nonexistent
2324
// variation.
24-
public static readonly EvalResult MalformedFlag =
25-
new EvalResult(ReasonKind.Error, "malformed flag", string.Empty);
25+
public static readonly EvalResult MalformedFlag = new(ReasonKind.Error, "malformed flag", Variation.Empty);
2626

27-
public static EvalResult FlagOff(string value)
27+
public static EvalResult FlagOff(Variation variation)
2828
{
29-
return new EvalResult(ReasonKind.Off, "flag off", value);
29+
return new EvalResult(ReasonKind.Off, "flag off", variation);
3030
}
3131

32-
public static EvalResult Targeted(string value)
32+
public static EvalResult Targeted(Variation variation)
3333
{
34-
return new EvalResult(ReasonKind.TargetMatch, "target match", value);
34+
return new EvalResult(ReasonKind.TargetMatch, "target match", variation);
3535
}
3636

37-
public static EvalResult RuleMatched(string value, string ruleName)
37+
public static EvalResult RuleMatched(string ruleName, Variation value)
3838
{
3939
return new EvalResult(ReasonKind.RuleMatch, $"match rule {ruleName}", value);
4040
}
4141

42-
public static EvalResult Fallthrough(string value)
42+
public static EvalResult Fallthrough(Variation variation)
43+
{
44+
return new EvalResult(ReasonKind.Fallthrough, "fall through targets and rules", variation);
45+
}
46+
47+
public EvalDetail<string> AsEvalDetail(string key)
4348
{
44-
return new EvalResult(ReasonKind.Fallthrough, "fall through targets and rules", value);
49+
return new EvalDetail<string>(key, Kind, Reason, Variation.Value, Variation.Id);
4550
}
4651
}
4752

src/FeatBit.ServerSdk/Evaluation/Evaluator.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public Evaluator(IMemoryStore store)
2727

2828
return Evaluate(flag, context.FbUser);
2929

30-
(EvalResult EvalResult, EvalEvent evalEvent) FlagNotFound() => (EvalResult.FlagNotFound, null);
30+
(EvalResult evalResult, EvalEvent evalEvent) FlagNotFound() => (EvalResult.FlagNotFound, null);
3131
}
3232

3333
public (EvalResult evalResult, EvalEvent evalEvent) Evaluate(FeatureFlag flag, FbUser user)
@@ -91,19 +91,19 @@ public Evaluator(IMemoryStore store)
9191

9292
return Fallthrough();
9393

94-
(EvalResult EvalResult, EvalEvent evalEvent) MalformedFlag() => (EvalResult.MalformedFlag, null);
94+
(EvalResult evalResult, EvalEvent evalEvent) MalformedFlag() => (EvalResult.MalformedFlag, null);
9595

96-
(EvalResult EvalResult, EvalEvent evalEvent) FlagOff(Variation variation) =>
97-
(EvalResult.FlagOff(variation.Value), new EvalEvent(user, flagKey, variation, false));
96+
(EvalResult evalResult, EvalEvent evalEvent) FlagOff(Variation variation) =>
97+
(EvalResult.FlagOff(variation), new EvalEvent(user, flagKey, variation, false));
9898

99-
(EvalResult EvalResult, EvalEvent evalEvent) Targeted(Variation variation, bool exptIncludeAllTargets) =>
100-
(EvalResult.Targeted(variation.Value), new EvalEvent(user, flagKey, variation, exptIncludeAllTargets));
99+
(EvalResult evalResult, EvalEvent evalEvent) Targeted(Variation variation, bool exptIncludeAllTargets) =>
100+
(EvalResult.Targeted(variation), new EvalEvent(user, flagKey, variation, exptIncludeAllTargets));
101101

102-
(EvalResult EvalResult, EvalEvent evalEvent) RuleMatched(TargetRule rule, RolloutVariation rolloutVariation)
102+
(EvalResult evalResult, EvalEvent evalEvent) RuleMatched(TargetRule rule, RolloutVariation rolloutVariation)
103103
{
104104
var variation = flag.GetVariation(rolloutVariation.Id);
105105

106-
var evalResult = EvalResult.RuleMatched(variation.Value, rule.Name);
106+
var evalResult = EvalResult.RuleMatched(rule.Name, variation);
107107

108108
var sendToExperiment = IsSendToExperiment(
109109
flag.ExptIncludeAllTargets,
@@ -116,11 +116,11 @@ public Evaluator(IMemoryStore store)
116116
return (evalResult, evalEvent);
117117
}
118118

119-
(EvalResult EvalResult, EvalEvent evalEvent) Fallthrough()
119+
(EvalResult evalResult, EvalEvent evalEvent) Fallthrough()
120120
{
121121
var variation = flag.GetVariation(defaultVariation.Id);
122122

123-
var evalResult = EvalResult.Fallthrough(variation.Value);
123+
var evalResult = EvalResult.Fallthrough(variation);
124124

125125
var sendToExperiment = IsSendToExperiment(
126126
flag.ExptIncludeAllTargets,

src/FeatBit.ServerSdk/FbClient.cs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,8 @@ public EvalDetail<string>[] GetAllVariations(FbUser user)
248248
.Find<FeatureFlag>(x => x.StoreKey.StartsWith(StoreKeys.FlagPrefix))
249249
.Select(flag =>
250250
{
251-
var evalResult = _evaluator.Evaluate(flag, user).evalResult;
252-
return new EvalDetail<string>(flag.Key, evalResult.Kind, evalResult.Reason, evalResult.Value);
251+
var (evalResult, _) = _evaluator.Evaluate(flag, user);
252+
return evalResult.AsEvalDetail(flag.Key);
253253
})
254254
.ToArray();
255255

@@ -290,7 +290,7 @@ private EvalDetail<TValue> EvaluateCore<TValue>(
290290
if (!Initialized)
291291
{
292292
// Flag evaluation before client initialized; always returning default value
293-
return new EvalDetail<TValue>(key, ReasonKind.ClientNotReady, "client not ready", defaultValue);
293+
return new EvalDetail<TValue>(key, ReasonKind.ClientNotReady, "client not ready", defaultValue, string.Empty);
294294
}
295295

296296
var ctx = new EvaluationContext
@@ -303,16 +303,17 @@ private EvalDetail<TValue> EvaluateCore<TValue>(
303303
if (evalResult.Kind == ReasonKind.Error)
304304
{
305305
// error happened when evaluate flag, return default value
306-
return new EvalDetail<TValue>(key, evalResult.Kind, evalResult.Reason, defaultValue);
306+
return new EvalDetail<TValue>(key, evalResult.Kind, evalResult.Reason, defaultValue, string.Empty);
307307
}
308308

309309
// record evaluation event
310310
_eventProcessor.Record(evalEvent);
311311

312-
return converter(evalResult.Value, out var typedValue)
313-
? new EvalDetail<TValue>(key, evalResult.Kind, evalResult.Reason, typedValue)
312+
var variation = evalResult.Variation;
313+
return converter(variation.Value, out var typedValue)
314+
? new EvalDetail<TValue>(key, evalResult.Kind, evalResult.Reason, typedValue, variation.Id)
314315
// type mismatch, return default value
315-
: new EvalDetail<TValue>(key, ReasonKind.WrongType, "type mismatch", defaultValue);
316+
: new EvalDetail<TValue>(key, ReasonKind.WrongType, "type mismatch", defaultValue, string.Empty);
316317
}
317318
}
318319
}

src/FeatBit.ServerSdk/IFbClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ public interface IFbClient
182182
/// Returns the variation of all feature flags for a given user, which can be passed to front-end code.
183183
/// </summary>
184184
/// <param name="user">a given user</param>
185-
/// <returns>an <see cref="EvalResult"/> array</returns>
185+
/// <returns>an <see cref="EvalDetail{T}"/> array</returns>
186186
EvalDetail<string>[] GetAllVariations(FbUser user);
187187

188188
/// <summary>

src/FeatBit.ServerSdk/Model/Variation.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,11 @@ internal sealed class Variation
55
public string Id { get; set; }
66

77
public string Value { get; set; }
8+
9+
public static readonly Variation Empty = new()
10+
{
11+
Id = string.Empty,
12+
Value = string.Empty
13+
};
814
}
915
}

tests/FeatBit.ServerSdk.Tests/Evaluation/EvaluatorTests.cs

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ namespace FeatBit.Sdk.Server.Evaluation;
55

66
public class EvaluatorTests
77
{
8+
private static readonly Variation TrueVariation = new() { Id = "trueId", Value = "true" };
9+
private static readonly Variation FalseVariation = new() { Id = "falseId", Value = "false" };
10+
811
[Fact]
912
public void EvaluateFlagNotFound()
1013
{
@@ -19,8 +22,8 @@ public void EvaluateFlagNotFound()
1922
var (evalResult, evalEvent) = evaluator.Evaluate(context);
2023

2124
Assert.Equal(ReasonKind.Error, evalResult.Kind);
22-
Assert.Equal(string.Empty, evalResult.Value);
2325
Assert.Equal("flag not found", evalResult.Reason);
26+
Assert.Equivalent(Variation.Empty, evalResult.Variation);
2427

2528
Assert.Null(evalEvent);
2629
}
@@ -33,7 +36,7 @@ public void EvaluateMalformedFlag()
3336
.Key("hello")
3437
.IsEnabled(false)
3538
.DisabledVariationId("not-exist-variation-id")
36-
.Variations(new Variation { Id = "trueId", Value = "true" })
39+
.Variations(TrueVariation)
3740
.Build();
3841
store.Populate(new[] { malformedFlag });
3942

@@ -47,8 +50,8 @@ public void EvaluateMalformedFlag()
4750
var (evalResult, evalEvent) = evaluator.Evaluate(context);
4851

4952
Assert.Equal(ReasonKind.Error, evalResult.Kind);
50-
Assert.Equal(string.Empty, evalResult.Value);
5153
Assert.Equal("malformed flag", evalResult.Reason);
54+
Assert.Equivalent(Variation.Empty, evalResult.Variation);
5255

5356
Assert.Null(evalEvent);
5457
}
@@ -61,7 +64,7 @@ public void EvaluateFlagOffResult()
6164
.Key("hello")
6265
.IsEnabled(false)
6366
.DisabledVariationId("trueId")
64-
.Variations(new Variation { Id = "trueId", Value = "true" })
67+
.Variations(TrueVariation)
6568
.Build();
6669
store.Populate(new[] { flag });
6770

@@ -75,8 +78,8 @@ public void EvaluateFlagOffResult()
7578
var (evalResult, evalEvent) = evaluator.Evaluate(context);
7679

7780
Assert.Equal(ReasonKind.Off, evalResult.Kind);
78-
Assert.Equal("true", evalResult.Value);
7981
Assert.Equal("flag off", evalResult.Reason);
82+
Assert.Equivalent(TrueVariation, evalResult.Variation);
8083

8184
// flag is off
8285
Assert.False(evalEvent.SendToExperiment);
@@ -87,11 +90,7 @@ public void EvaluateTargetedResult()
8790
{
8891
var store = new DefaultMemoryStore();
8992

90-
var variations = new Variation[]
91-
{
92-
new() { Id = "trueId", Value = "true" },
93-
new() { Id = "falseId", Value = "false" }
94-
};
93+
var variations = new[] { TrueVariation, FalseVariation };
9594
var targetUser = new TargetUser
9695
{
9796
VariationId = "falseId", KeyIds = new List<string> { "u1" }
@@ -115,8 +114,8 @@ public void EvaluateTargetedResult()
115114
var (evalResult, evalEvent) = evaluator.Evaluate(context);
116115

117116
Assert.Equal(ReasonKind.TargetMatch, evalResult.Kind);
118-
Assert.Equal("false", evalResult.Value);
119117
Assert.Equal("target match", evalResult.Reason);
118+
Assert.Equivalent(FalseVariation, evalResult.Variation);
120119

121120
// ExptIncludeAllTargets is true by default
122121
Assert.True(evalEvent.SendToExperiment);
@@ -127,11 +126,7 @@ public void EvaluatorRuleMatchedResult()
127126
{
128127
var store = new DefaultMemoryStore();
129128

130-
var variations = new Variation[]
131-
{
132-
new() { Id = "trueId", Value = "true" },
133-
new() { Id = "falseId", Value = "false" }
134-
};
129+
var variations = new[] { TrueVariation, FalseVariation };
135130
var customRule = new TargetRule
136131
{
137132
Name = "open for vip & svip",
@@ -178,8 +173,8 @@ public void EvaluatorRuleMatchedResult()
178173
var (evalResult, evalEvent) = evaluator.Evaluate(context);
179174

180175
Assert.Equal(ReasonKind.RuleMatch, evalResult.Kind);
181-
Assert.Equal("true", evalResult.Value);
182176
Assert.Equal($"match rule {customRule.Name}", evalResult.Reason);
177+
Assert.Equivalent(TrueVariation, evalResult.Variation);
183178

184179
// customRule.IncludedInExpt is false
185180
Assert.False(evalEvent.SendToExperiment);
@@ -230,8 +225,8 @@ public void EvaluateFallthroughResult()
230225
var (evalResult, evalEvent) = evaluator.Evaluate(context);
231226

232227
Assert.Equal(ReasonKind.Fallthrough, evalResult.Kind);
233-
Assert.Equal("false", evalResult.Value);
234228
Assert.Equal("fall through targets and rules", evalResult.Reason);
229+
Assert.Equivalent(FalseVariation, evalResult.Variation);
235230

236231
Assert.True(evalEvent.SendToExperiment);
237232
}

tests/FeatBit.ServerSdk.Tests/FbClientTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public void GetVariationDetail()
6262
var variationDetail = client.BoolVariationDetail("returns-true", user);
6363
Assert.Equal("returns-true", variationDetail.Key);
6464
Assert.True(variationDetail.Value);
65+
Assert.Equal("3da96792-debf-4878-905a-c9b5f9178cd0", variationDetail.ValueId);
6566
Assert.Equal(ReasonKind.Fallthrough, variationDetail.Kind);
6667
Assert.Equal("fall through targets and rules", variationDetail.Reason);
6768

@@ -80,6 +81,7 @@ public void GetAllVariations()
8081
var result0 = results[0];
8182
Assert.Equal("returns-true", result0.Key);
8283
Assert.Equal("true", result0.Value);
84+
Assert.Equal("3da96792-debf-4878-905a-c9b5f9178cd0", result0.ValueId);
8385
Assert.Equal(ReasonKind.Fallthrough, result0.Kind);
8486
Assert.Equal("fall through targets and rules", result0.Reason);
8587
}

0 commit comments

Comments
 (0)