Skip to content

Commit

Permalink
feat: Add support for the TargetingKey field and setter.
Browse files Browse the repository at this point in the history
  • Loading branch information
kinyoklion committed Jun 4, 2024
1 parent dbbdf94 commit 4b85d20
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 18 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ The `kind` attribute should be a string containing only contain ASCII letters, n

The OpenFeature specification allows for an optional targeting key, but LaunchDarkly requires a key for evaluation. A targeting key must be specified for each context being evaluated. It may be specified using either `targetingKey`, as it is in the OpenFeature specification, or `key`, which is the typical LaunchDarkly identifier for the targeting key. If a `targetingKey` and a `key` are specified, then the `targetingKey` will take precedence.

The targeting key may also be specified using `SetTargetingKey`. Currently, if `SetTargetingKey` is used, and a `targetingKey` attribute exists, then the attribute will be used instead.

There are several other attributes which have special functionality within a single or multi-context.
- A key of `privateAttributes`. Must be an array of string values. [Equivalent to the 'Private' builder method in the SDK.](https://launchdarkly.github.io/dotnet-server-sdk/api/LaunchDarkly.Sdk.ContextBuilder.html#LaunchDarkly_Sdk_ContextBuilder_Private_System_String___)
- A key of `anonymous`. Must be a boolean value. [Equivalent to the 'Anonymous' builder method in the SDK.](https://launchdarkly.github.io/dotnet-server-sdk/api/LaunchDarkly.Sdk.Context.html#LaunchDarkly_Sdk_Context_Anonymous)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,14 @@ public Context ToLdContext(EvaluationContext evaluationContext)
}
// Else, there is no kind, so we are going to assume a user.

return BuildSingleLdContext(evaluationContext.AsDictionary(), kindString);
var targetingKey = evaluationContext.TargetingKey;
var asDictionary = evaluationContext.AsDictionary();
if (!string.IsNullOrEmpty(targetingKey) && !asDictionary.ContainsKey("targetingKey"))
{
asDictionary = asDictionary.Add("targetingKey", new Value(targetingKey));
}

return BuildSingleLdContext(asDictionary, kindString);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,50 @@ public void ItCanHandleBuiltInAttributes()
Assert.Empty(_logCapture.GetMessages());
}

[Fact]
public void ItHandlesBuiltInTargetingKey()
{
var evaluationContext = EvaluationContext.Builder()
.SetTargetingKey("the-key")
.Set("name", "name")
.Set("firstName", "firstName")
.Set("lastName", "lastName")
.Set("email", "email")
.Set("avatar", "avatar")
.Set("ip", "ip")
.Set("country", "country")
.Set("anonymous", true).Build();

var convertedUser = _converter.ToLdContext(evaluationContext);

var expectedUser = User.Builder("the-key")
.Name("name")
.FirstName("firstName")
.LastName("lastName")
.Email("email")
.Avatar("avatar")
.IPAddress("ip")
.Country("country")
.Anonymous(true)
.Build();

Assert.Equal(Context.FromUser(expectedUser), convertedUser);
// Nothing is wrong with this, so it shouldn't have produced any messages.
Assert.Empty(_logCapture.GetMessages());
}

[Fact]
public void ItAllowsNullForBuiltInAttributes()
{
var evaluationContext = EvaluationContext.Builder()
.Set("targetingKey", "the-key")
.Set("name", (string) null)
.Set("firstName", (string) null)
.Set("lastName", (string) null)
.Set("email", (string) null)
.Set("avatar", (string) null)
.Set("ip", (string) null)
.Set("country", (string) null)
.Set("name", (string)null)
.Set("firstName", (string)null)
.Set("lastName", (string)null)
.Set("email", (string)null)
.Set("avatar", (string)null)
.Set("ip", (string)null)
.Set("country", (string)null)
// Cannot just pass in null, cannot pass in a nullable bool, have to either case to a reference type like
// string, or construct a value instance and pass that.
.Set("anonymous", new Value())
Expand Down Expand Up @@ -156,14 +188,14 @@ public void ItCanBuildASingleContext()
.Set("targetingKey", "my-org-key")
.Set("anonymous", true)
.Set("myCustomAttribute", "myCustomValue")
.Set("privateAttributes", new Value(new List<Value>{new Value("myCustomAttribute")}))
.Set("privateAttributes", new Value(new List<Value> { new Value("myCustomAttribute") }))
.Build();

var expectedContext = Context.Builder(ContextKind.Of("organization"), "my-org-key")
.Name("the-org-name")
.Anonymous(true)
.Set("myCustomAttribute", "myCustomValue")
.Private(new[] {"myCustomAttribute"})
.Private(new[] { "myCustomAttribute" })
.Build();

Assert.Equal(expectedContext, _converter.ToLdContext(evaluationContext));
Expand All @@ -176,22 +208,23 @@ public void ItCanBuildAMultiContext()
.Set("kind", "multi")
.Set("organization", new Structure(new Dictionary<string, Value>
{
{"targetingKey", new Value("my-org-key")},
{"name", new Value("the-org-name")},
{"myCustomAttribute", new Value("myAttributeValue")},
{"privateAttributes", new Value(new List<Value>{new Value("myCustomAttribute")})}
{ "targetingKey", new Value("my-org-key") },
{ "name", new Value("the-org-name") },
{ "myCustomAttribute", new Value("myAttributeValue") },
{ "privateAttributes", new Value(new List<Value> { new Value("myCustomAttribute") }) }
}))
.Set("user", new Structure(new Dictionary<string, Value> {
{"targetingKey", new Value("my-user-key")},
{"anonymous", new Value(true)}
.Set("user", new Structure(new Dictionary<string, Value>
{
{ "targetingKey", new Value("my-user-key") },
{ "anonymous", new Value(true) }
}))
.Build();

var expectedContext = Context.MultiBuilder()
.Add(Context.Builder(ContextKind.Of("organization"), "my-org-key")
.Name("the-org-name")
.Set("myCustomAttribute", "myAttributeValue")
.Private(new []{"myCustomAttribute"})
.Private(new[] { "myCustomAttribute" })
.Build())
.Add(Context.Builder("my-user-key")
.Anonymous(true)
Expand Down

0 comments on commit 4b85d20

Please sign in to comment.