diff --git a/src/LaunchDarkly.OpenFeature.ServerProvider/EvalContextConverter.cs b/src/LaunchDarkly.OpenFeature.ServerProvider/EvalContextConverter.cs
index 210dc7a..b361cab 100644
--- a/src/LaunchDarkly.OpenFeature.ServerProvider/EvalContextConverter.cs
+++ b/src/LaunchDarkly.OpenFeature.ServerProvider/EvalContextConverter.cs
@@ -33,6 +33,14 @@ public EvalContextConverter(Logger log)
private static string InvalidTypeMessage(string attribute, string type) => $"The attribute '{attribute}' " +
$"must be of type {type}";
+ ///
+ /// Get the value as a string, or null if it is not a string or is empty.
+ ///
+ /// The value to inspect
+ /// The non-empty string value, or null
+ private static string NonEmptyString(Value value) =>
+ value != null && value.IsString && value.AsString.Length != 0 ? value.AsString : null;
+
///
/// Extract a string value and log an error if the value was not a string.
///
@@ -196,10 +204,17 @@ private Context BuildSingleLdContext(IImmutableDictionary attribu
{
// targetingKey is in the specification, so it takes precedence.
attributes.TryGetValue("key", out var keyAttr);
- attributes.TryGetValue("targetingKey", out var targetingKey);
- var finalKey = (targetingKey ?? keyAttr)?.AsString;
+ attributes.TryGetValue("targetingKey", out var targetingKeyAttr);
+ var targetingKey = NonEmptyString(targetingKeyAttr);
+ var keyFromAttr = NonEmptyString(keyAttr);
+ var finalKey = targetingKey ?? keyFromAttr;
+
+ if (keyAttr != null && !keyAttr.IsNull && !keyAttr.IsString)
+ {
+ _log.Warn("A non-string 'key' attribute was provided.");
+ }
- if (keyAttr != null && targetingKey != null)
+ if (keyFromAttr != null && targetingKey != null)
{
_log.Warn("The EvaluationContext contained both a 'targetingKey' and a 'key' attribute. The 'key'" +
" attribute will be discarded.");
diff --git a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/EvalContextConverterTests.cs b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/EvalContextConverterTests.cs
index 01b8478..fdc2025 100644
--- a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/EvalContextConverterTests.cs
+++ b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/EvalContextConverterTests.cs
@@ -179,6 +179,46 @@ public void ItUsesTheTargetingKeyInFavorOfKey()
Assert.Equal("targeting-key", _converter.ToLdContext(evaluationContext).Key);
}
+ [Fact]
+ public void ItIgnoresANonStringKey()
+ {
+ var evaluationContext = EvaluationContext.Builder()
+ .Set("key", 42)
+ .Build();
+
+ Assert.False(_converter.ToLdContext(evaluationContext).Valid);
+ Assert.True(_logCapture.HasMessageWithText(LogLevel.Warn,
+ "A non-string 'key' attribute was provided."));
+ Assert.True(_logCapture.HasMessageWithText(LogLevel.Error,
+ "The EvaluationContext must contain either a 'targetingKey' or a 'key' and the type" +
+ " must be a string."));
+ }
+
+ [Fact]
+ public void ItUsesTheKeyAttributeWhenTheTargetingKeyIsEmpty()
+ {
+ var evaluationContext = EvaluationContext.Builder()
+ .Set("targetingKey", "")
+ .Set("key", "the-key")
+ .Build();
+
+ Assert.Equal("the-key", _converter.ToLdContext(evaluationContext).Key);
+ Assert.Empty(_logCapture.GetMessages());
+ }
+
+ [Fact]
+ public void ItLogsAnErrorWhenTheOnlyKeyIsEmpty()
+ {
+ var evaluationContext = EvaluationContext.Builder()
+ .Set("targetingKey", "")
+ .Build();
+
+ Assert.False(_converter.ToLdContext(evaluationContext).Valid);
+ Assert.True(_logCapture.HasMessageWithText(LogLevel.Error,
+ "The EvaluationContext must contain either a 'targetingKey' or a 'key' and the type" +
+ " must be a string."));
+ }
+
[Fact]
public void ItCanBuildASingleContext()
{