Skip to content

Commit

Permalink
fix: Add targeting key (#231)
Browse files Browse the repository at this point in the history
Fixes #230

---------

Signed-off-by: Roelof Blom <[email protected]>
Signed-off-by: Roelof Blom <[email protected]>
Signed-off-by: Austin Drenski <[email protected]>
Signed-off-by: Todd Baert <[email protected]>
Co-authored-by: Austin Drenski <[email protected]>
Co-authored-by: Todd Baert <[email protected]>
  • Loading branch information
3 people authored Feb 12, 2024
1 parent 1082094 commit d792b32
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 10 deletions.
10 changes: 9 additions & 1 deletion src/OpenFeature/Model/EvaluationContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ public sealed class EvaluationContext
/// <summary>
/// Internal constructor used by the builder.
/// </summary>
/// <param name="targetingKey">The targeting key</param>
/// <param name="content">The content of the context.</param>
internal EvaluationContext(Structure content)
internal EvaluationContext(string targetingKey, Structure content)
{
this.TargetingKey = targetingKey;
this._structure = content;
}

Expand All @@ -28,6 +30,7 @@ internal EvaluationContext(Structure content)
private EvaluationContext()
{
this._structure = Structure.Empty;
this.TargetingKey = string.Empty;
}

/// <summary>
Expand Down Expand Up @@ -83,6 +86,11 @@ public IImmutableDictionary<string, Value> AsDictionary()
/// </summary>
public int Count => this._structure.Count;

/// <summary>
/// Returns the targeting key for the context.
/// </summary>
public string TargetingKey { get; }

/// <summary>
/// Return an enumerator for all values
/// </summary>
Expand Down
32 changes: 31 additions & 1 deletion src/OpenFeature/Model/EvaluationContextBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,24 @@ public sealed class EvaluationContextBuilder
{
private readonly StructureBuilder _attributes = Structure.Builder();

internal string TargetingKey { get; private set; }

/// <summary>
/// Internal to only allow direct creation by <see cref="EvaluationContext.Builder()"/>.
/// </summary>
internal EvaluationContextBuilder() { }

/// <summary>
/// Set the targeting key for the context.
/// </summary>
/// <param name="targetingKey">The targeting key</param>
/// <returns>This builder</returns>
public EvaluationContextBuilder SetTargetingKey(string targetingKey)
{
this.TargetingKey = targetingKey;
return this;
}

/// <summary>
/// Set the key to the given <see cref="Value"/>.
/// </summary>
Expand Down Expand Up @@ -125,6 +138,23 @@ public EvaluationContextBuilder Set(string key, DateTime value)
/// <returns>This builder</returns>
public EvaluationContextBuilder Merge(EvaluationContext context)
{
string newTargetingKey = "";

if (!string.IsNullOrWhiteSpace(TargetingKey))
{
newTargetingKey = TargetingKey;
}

if (!string.IsNullOrWhiteSpace(context.TargetingKey))
{
newTargetingKey = context.TargetingKey;
}

if (!string.IsNullOrWhiteSpace(newTargetingKey))
{
this.TargetingKey = newTargetingKey;
}

foreach (var kvp in context)
{
this.Set(kvp.Key, kvp.Value);
Expand All @@ -139,7 +169,7 @@ public EvaluationContextBuilder Merge(EvaluationContext context)
/// <returns>An immutable <see cref="EvaluationContext"/></returns>
public EvaluationContext Build()
{
return new EvaluationContext(this._attributes.Build());
return new EvaluationContext(this.TargetingKey, this._attributes.Build());
}
}
}
13 changes: 6 additions & 7 deletions test/OpenFeature.E2ETests/Steps/EvaluationStepDefinitions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,11 @@ public void Giventhevariantshouldbeandthereasonshouldbe(string expectedVariant,
[When(@"context contains keys ""(.*)"", ""(.*)"", ""(.*)"", ""(.*)"" with values ""(.*)"", ""(.*)"", (.*), ""(.*)""")]
public void Whencontextcontainskeyswithvalues(string field1, string field2, string field3, string field4, string value1, string value2, int value3, string value4)
{
var attributes = ImmutableDictionary.CreateBuilder<string, Value>();
attributes.Add(field1, new Value(value1));
attributes.Add(field2, new Value(value2));
attributes.Add(field3, new Value(value3));
attributes.Add(field4, new Value(bool.Parse(value4)));
this.context = new EvaluationContext(new Structure(attributes));
this.context = new EvaluationContextBuilder()
.Set(field1, value1)
.Set(field2, value2)
.Set(field3, value3)
.Set(field4, bool.Parse(value4)).Build();
}

[When(@"a flag with key ""(.*)"" is evaluated with default value ""(.*)""")]
Expand All @@ -225,7 +224,7 @@ public void Thentheresolvedstringresponseshouldbe(string expected)
[Then(@"the resolved flag value is ""(.*)"" when the context is empty")]
public void Giventheresolvedflagvalueiswhenthecontextisempty(string expected)
{
string emptyContextValue = client.GetStringValue(contextAwareFlagKey, contextAwareDefaultValue, new EvaluationContext(new Structure(ImmutableDictionary<string, Value>.Empty))).Result;
string emptyContextValue = client.GetStringValue(contextAwareFlagKey, contextAwareDefaultValue, new EvaluationContextBuilder().Build()).Result;
Assert.Equal(expected, emptyContextValue);
}

Expand Down
37 changes: 36 additions & 1 deletion test/OpenFeature.Tests/OpenFeatureEvaluationContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,42 @@ public void Should_Merge_Two_Contexts()
.Set("key1", "value1");
var contextBuilder2 = new EvaluationContextBuilder()
.Set("key2", "value2");

var context1 = contextBuilder1.Merge(contextBuilder2.Build()).Build();

Assert.Equal(2, context1.Count);
Assert.Equal("value1", context1.GetValue("key1").AsString);
Assert.Equal("value2", context1.GetValue("key2").AsString);
}

[Fact]
public void Should_Change_TargetingKey_From_OverridingContext()
{
var contextBuilder1 = new EvaluationContextBuilder()
.Set("key1", "value1")
.SetTargetingKey("targeting_key");
var contextBuilder2 = new EvaluationContextBuilder()
.Set("key2", "value2")
.SetTargetingKey("overriding_key");

var mergeContext = contextBuilder1.Merge(contextBuilder2.Build()).Build();

Assert.Equal("overriding_key", mergeContext.TargetingKey);
}

[Fact]
public void Should_Retain_TargetingKey_When_OverridingContext_TargetingKey_Value_IsEmpty()
{
var contextBuilder1 = new EvaluationContextBuilder()
.Set("key1", "value1")
.SetTargetingKey("targeting_key");
var contextBuilder2 = new EvaluationContextBuilder()
.Set("key2", "value2");

var mergeContext = contextBuilder1.Merge(contextBuilder2.Build()).Build();

Assert.Equal("targeting_key", mergeContext.TargetingKey);
}

[Fact]
[Specification("3.2.2", "Evaluation context MUST be merged in the order: API (global; lowest precedence) - client - invocation - before hooks (highest precedence), with duplicate values being overwritten.")]
public void Should_Merge_TwoContexts_And_Override_Duplicates_With_RightHand_Context()
Expand Down Expand Up @@ -51,6 +79,8 @@ public void EvaluationContext_Should_All_Types()
var now = fixture.Create<DateTime>();
var structure = fixture.Create<Structure>();
var contextBuilder = new EvaluationContextBuilder()
.SetTargetingKey("targeting_key")
.Set("targeting_key", "userId")
.Set("key1", "value")
.Set("key2", 1)
.Set("key3", true)
Expand All @@ -60,6 +90,11 @@ public void EvaluationContext_Should_All_Types()

var context = contextBuilder.Build();

context.TargetingKey.Should().Be("targeting_key");
var targetingKeyValue = context.GetValue(context.TargetingKey);
targetingKeyValue.IsString.Should().BeTrue();
targetingKeyValue.AsString.Should().Be("userId");

var value1 = context.GetValue("key1");
value1.IsString.Should().BeTrue();
value1.AsString.Should().Be("value");
Expand Down

0 comments on commit d792b32

Please sign in to comment.