diff --git a/src/LaunchDarkly.OpenFeature.ServerProvider/EvalContextConverter.cs b/src/LaunchDarkly.OpenFeature.ServerProvider/EvalContextConverter.cs index b361cab..85120a9 100644 --- a/src/LaunchDarkly.OpenFeature.ServerProvider/EvalContextConverter.cs +++ b/src/LaunchDarkly.OpenFeature.ServerProvider/EvalContextConverter.cs @@ -93,6 +93,40 @@ private void Extract(string key, LdValue value, Func sette _log.Error(InvalidTypeMessage(key, "bool")); } + /// + /// Extract private attributes and log an error if they are not a list of strings. + /// + /// The value to extract + /// The context builder to add the private attributes to + private void ExtractPrivateAttributes(LdValue value, ContextBuilder builder) + { + if (value.IsNull) + { + // Ignore null values. + return; + } + + if (value.Type != LdValueType.Array) + { + _log.Error(InvalidTypeMessage("privateAttributes", "array")); + return; + } + + var items = value.AsList(LdValue.Convert.Json); + var privateAttributes = items.Where(item => item.IsString).Select(item => item.AsString).ToArray(); + + if (privateAttributes.Length != items.Count) + { + _log.Error("'privateAttributes' must be an array of only string values. The non-string" + + " values have been dropped and the remaining values have been applied."); + } + + if (privateAttributes.Length != 0) + { + builder.Private(privateAttributes); + } + } + /// /// Extract a value and add it to a context builder. /// @@ -116,7 +150,7 @@ private void ProcessValue(string key, Value value, ContextBuilder builder) Extract(key, ldValue, builder.Anonymous); break; case "privateAttributes": - builder.Private(ldValue.AsList(LdValue.Convert.String).ToArray()); + ExtractPrivateAttributes(ldValue, builder); break; default: // Was not a built-in attribute. diff --git a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/EvalContextConverterTests.cs b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/EvalContextConverterTests.cs index fdc2025..37fc4d8 100644 --- a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/EvalContextConverterTests.cs +++ b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/EvalContextConverterTests.cs @@ -179,6 +179,58 @@ public void ItUsesTheTargetingKeyInFavorOfKey() Assert.Equal("targeting-key", _converter.ToLdContext(evaluationContext).Key); } + [Fact] + public void ItLogsAnErrorWhenPrivateAttributesIsNotAnArray() + { + var evaluationContext = EvaluationContext.Builder() + .Set("targetingKey", "my-key") + .Set("myCustomAttribute", "myCustomValue") + .Set("privateAttributes", "myCustomAttribute") + .Build(); + + var expectedContext = Context.Builder("my-key") + .Set("myCustomAttribute", "myCustomValue") + .Build(); + + Assert.Equal(expectedContext, _converter.ToLdContext(evaluationContext)); + Assert.True(_logCapture.HasMessageWithText(LogLevel.Error, + "The attribute 'privateAttributes' must be of type array")); + } + + [Fact] + public void ItOmitsNonStringPrivateAttributes() + { + var evaluationContext = EvaluationContext.Builder() + .Set("targetingKey", "my-key") + .Set("privateAttributes", new Value(new List + { + new Value("myCustomAttribute"), + new Value(17) + })) + .Build(); + + var expectedContext = Context.Builder("my-key") + .Private("myCustomAttribute") + .Build(); + + Assert.Equal(expectedContext, _converter.ToLdContext(evaluationContext)); + Assert.True(_logCapture.HasMessageWithText(LogLevel.Error, + "'privateAttributes' must be an array of only string values. The non-string" + + " values have been dropped and the remaining values have been applied.")); + } + + [Fact] + public void ItAllowsEmptyPrivateAttributes() + { + var evaluationContext = EvaluationContext.Builder() + .Set("targetingKey", "my-key") + .Set("privateAttributes", new Value(new List())) + .Build(); + + Assert.Equal(Context.Builder("my-key").Build(), _converter.ToLdContext(evaluationContext)); + Assert.Empty(_logCapture.GetMessages()); + } + [Fact] public void ItIgnoresANonStringKey() {