Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Add targeting key #231

Merged
merged 8 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading