diff --git a/README.md b/README.md index f2da3b4..f16eb4f 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,97 @@ For information on using the OpenFeature client please refer to the [OpenFeature ## OpenFeature Specific Considerations -When evaluating a `User` with the LaunchDarkly Server-Side SDK for .NET a string `key` attribute would normally be required. When using OpenFeature the `targetingKey` attribute should be used instead of `key`. If a `key` attribute is provided in the `EvaluationContext`, then it will be discarded in favor of `targetingKey`. If a `targetingKey` is not provided, or if the `EvaluationContext` is omitted entirely, then the `defaultValue` will be returned from OpenFeature evaluation methods. +LaunchDarkly evaluates contexts, and it can either evaluate a single-context, or a multi-context. When using OpenFeature both single and multi-contexts must be encoded into a single `EvaluationContext`. This is accomplished by looking for an attribute named `kind` in the `EvaluationContext`. -Other fields normally included in a `User` may be added to the `EvaluationContext`. Any `custom` attributes can be added to the top level of the evaluation context, and they will operate as if they were `custom` attributes on an `User`. Attributes which are typically top level on an `LDUser` should be of the same types that are specified for a `User` or they will not operate as intended. +There are 4 different scenarios related to the `kind`: +1. There is no `kind` attribute. In this case the provider will treat the context as a single context containing a "user" kind. +2. There is a `kind` attribute, and the value of that attribute is "multi". This will indicate to the provider that the context is a multi-context. +3. There is a `kind` attribute, and the value of that attribute is a string other than "multi". This will indicate to the provider a single context of the kind specified. +4. There is a `kind` attribute, and the attribute is not a string. In this case the value of the attribute will be discarded, and the context will be treated as a "user". An error message will be logged. -If a top level `custom` attribute is defined on the `EvaluationContext`, then that will be a `custom` attribute inside `custom` for a `User`. +The `kind` attribute should be a string containing only contain ASCII letters, numbers, `.`, `_` or `-`. + +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. + +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) +- A key of `name`. Must be a string. [Equivalent to the 'Name' builder method in the SDK.](https://launchdarkly.github.io/dotnet-server-sdk/api/LaunchDarkly.Sdk.ContextBuilder.html#LaunchDarkly_Sdk_ContextBuilder_Name_System_String_) + +### Examples + +#### A single user context + +```csharp +var evaluationContext = EvaluationContext.Builder() + .Set("targetingKey", "my-user-key") // Could also use "key" instead of "targetingKey". + .Build(); +``` + +#### A single context of kind "organization" + +```csharp +var evaluationContext = EvaluationContext.Builder() + .Set("kind", "organization") + .Set("targetingKey", "my-org-key") // Could also use "key" instead of "targetingKey". + .Build(); +``` + +#### A multi-context containing a "user" and an "organization" + +```csharp +var evaluationContext = EvaluationContext.Builder() + .Set("kind", "multi") // Lets the provider know this is a multi-context + // Every other top level attribute should be a structure representing + // individual contexts of the multi-context. + // (non-conforming attributes will be ignored and a warning logged). + .Set("organization", new Structure(new Dictionary + { + {"targetingKey", new Value("my-org-key")}, + {"name", new Value("the-org-name")}, + {"myCustomAttribute", new Value("myAttributeValue")} + })) + .Set("user", new Structure(new Dictionary { + {"targetingKey", new Value("my-user-key")}, + })) + .Build(); +``` + +#### Setting private attributes in a single context + +```csharp +var evaluationContext = EvaluationContext.Builder() + .Set("kind", "organization") + .Set("name", "the-org-name") + .Set("targetingKey", "my-org-key") + .Set("anonymous", true) + .Set("myCustomAttribute", "myCustomValue") + .Set("privateAttributes", new Value(new List{new Value("myCustomAttribute")})) + .Build(); +``` + +#### Setting private attributes in a multi-context + +```csharp +var evaluationContext = EvaluationContext.Builder() + .Set("kind", "multi") + .Set("organization", new Structure(new Dictionary + { + {"targetingKey", new Value("my-org-key")}, + {"name", new Value("the-org-name")}, + // This will ONLY apply to the "organization" attributes. + {"privateAttributes", new Value(new List{new Value("myCustomAttribute")})} + // This attribute will be private. + {"myCustomAttribute", new Value("myAttributeValue")}, + })) + .Set("user", new Structure(new Dictionary { + {"targetingKey", new Value("my-user-key")}, + {"anonymous", new Value(true)}, + // This attribute will not be private. + {"myCustomAttribute", new Value("myAttributeValue")}, + })) + .Build(); +``` ## Learn more diff --git a/src/LaunchDarkly.OpenFeature.ServerProvider/EvalContextConverter.cs b/src/LaunchDarkly.OpenFeature.ServerProvider/EvalContextConverter.cs index 9bf6c8c..2bdce34 100644 --- a/src/LaunchDarkly.OpenFeature.ServerProvider/EvalContextConverter.cs +++ b/src/LaunchDarkly.OpenFeature.ServerProvider/EvalContextConverter.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using LaunchDarkly.Logging; using LaunchDarkly.Sdk; using OpenFeature.Model; @@ -6,7 +9,7 @@ namespace LaunchDarkly.OpenFeature.ServerProvider { /// - /// Class which converts objects into objects. + /// Class which converts objects into objects. /// internal class EvalContextConverter { @@ -39,7 +42,7 @@ private static string InvalidTypeMessage(string attribute, string type) => $"The /// A method to call with the extracted value. /// This will only be called if the type was correct. /// - private void Extract(string key, LdValue value, Func setter) + private void Extract(string key, LdValue value, Func setter) { if (value.IsNull) { @@ -65,7 +68,7 @@ private void Extract(string key, LdValue value, Func sette /// A method to call with the extracted value. /// This will only be called if the type was correct. /// - private void Extract(string key, LdValue value, Func setter) + private void Extract(string key, LdValue value, Func setter) { if (value.IsNull) { @@ -83,12 +86,12 @@ private void Extract(string key, LdValue value, Func setter) } /// - /// Extract a value and add it to a user builder. + /// Extract a value and add it to a context builder. /// - /// The key to add to the user if the value can be extracted + /// The key to add to the context if the value can be extracted /// The value to extract - /// The user builder to add the value to - private void ProcessValue(string key, Value value, IUserBuilder builder) + /// The context builder to add the value to + private void ProcessValue(string key, Value value, ContextBuilder builder) { var ldValue = value.ToLdValue(); @@ -98,50 +101,95 @@ private void ProcessValue(string key, Value value, IUserBuilder builder) case "targetingKey": case "key": break; - case "secondary": - Extract(key, ldValue, builder.Secondary); - break; case "name": Extract(key, ldValue, builder.Name); break; - case "firstName": - Extract(key, ldValue, builder.FirstName); - break; - case "lastName": - Extract(key, ldValue, builder.LastName); - break; - case "email": - Extract(key, ldValue, builder.Email); - break; - case "avatar": - Extract(key, ldValue, builder.Avatar); - break; - case "ip": - Extract(key, ldValue, builder.IPAddress); - break; - case "country": - Extract(key, ldValue, builder.Country); - break; case "anonymous": Extract(key, ldValue, builder.Anonymous); break; + case "privateAttributes": + builder.Private(ldValue.AsList(LdValue.Convert.String).ToArray()); + break; default: // Was not a built-in attribute. - builder.Custom(key, ldValue); + builder.Set(key, ldValue); break; } } /// - /// Convert an into a . + /// Convert an into a . + /// + /// The evaluation context to convert + /// A converted context + public Context ToLdContext(EvaluationContext evaluationContext) + { + // Use the kind to determine the evaluation context shape. + // If there is no kind at all, then we make a single context of "user" kind. + evaluationContext.TryGetValue("kind", out var kind); + + var kindString = "user"; + // A multi-context. + if (kind != null && kind.AsString == "multi") + { + return BuildMultiLdContext(evaluationContext); + } + // Single context with specified kind. + else if (kind != null && kind.IsString) + { + kindString = kind.AsString; + } + // The kind was not a string. + else if (kind != null && !kind.IsString) + { + _log.Warn("The EvaluationContext contained an invalid kind and it will be discarded."); + } + // Else, there is no kind, so we are going to assume a user. + + return BuildSingleLdContext(evaluationContext.AsDictionary(), kindString); + } + + /// + /// Convert an evaluation context into a multi-context. /// /// The evaluation context to convert - /// A converted user - public User ToLdUser(EvaluationContext evaluationContext) + /// A converted multi-context + private Context BuildMultiLdContext(EvaluationContext evaluationContext) + { + var multiBuilder = Context.MultiBuilder(); + foreach (var pair in evaluationContext.AsDictionary()) + { + // Don't need to inspect the "kind" key. + if (pair.Key == "kind") continue; + + var kind = pair.Key; + var attributes = pair.Value; + + if (!attributes.IsStructure) + { + _log.Warn("Top level attributes in a multi-kind context should be Structure types."); + continue; + } + + multiBuilder.Add(BuildSingleLdContext(attributes.AsStructure.AsDictionary(), kind)); + } + + + return multiBuilder.Build(); + } + + /// + /// Construct a single context from an immutable dictionary of attributes. + /// This can either be the entirety of a single context, or a part of a multi-context. + /// + /// The attributes to use when building the context + /// The kind of the built context + /// A converted context + private Context BuildSingleLdContext(IImmutableDictionary attributes, string kindString) { - // targetingKey is the specification, so it takes precedence. - evaluationContext.TryGetValue("key", out var keyAttr); - evaluationContext.TryGetValue("targetingKey", out var targetingKey); + // 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; if (keyAttr != null && targetingKey != null) @@ -156,16 +204,16 @@ public User ToLdUser(EvaluationContext evaluationContext) "must be a string."); } - var userBuilder = User.Builder(finalKey); - foreach (var kvp in evaluationContext) + var contextBuilder = Context.Builder(ContextKind.Of(kindString), finalKey); + foreach (var kvp in attributes) { var key = kvp.Key; var value = kvp.Value; - ProcessValue(key, value, userBuilder); + ProcessValue(key, value, contextBuilder); } - return userBuilder.Build(); + return contextBuilder.Build(); } } } diff --git a/src/LaunchDarkly.OpenFeature.ServerProvider/LaunchDarkly.OpenFeature.ServerProvider.csproj b/src/LaunchDarkly.OpenFeature.ServerProvider/LaunchDarkly.OpenFeature.ServerProvider.csproj index ffa0049..753e061 100644 --- a/src/LaunchDarkly.OpenFeature.ServerProvider/LaunchDarkly.OpenFeature.ServerProvider.csproj +++ b/src/LaunchDarkly.OpenFeature.ServerProvider/LaunchDarkly.OpenFeature.ServerProvider.csproj @@ -38,7 +38,7 @@ - + diff --git a/src/LaunchDarkly.OpenFeature.ServerProvider/Provider.cs b/src/LaunchDarkly.OpenFeature.ServerProvider/Provider.cs index 9b249bf..21d7336 100644 --- a/src/LaunchDarkly.OpenFeature.ServerProvider/Provider.cs +++ b/src/LaunchDarkly.OpenFeature.ServerProvider/Provider.cs @@ -3,6 +3,7 @@ using LaunchDarkly.Sdk; using LaunchDarkly.Sdk.Server; using LaunchDarkly.Sdk.Server.Interfaces; +using LaunchDarkly.Sdk.Server.Subsystems; using OpenFeature; using OpenFeature.Model; @@ -39,7 +40,7 @@ public Provider(ILdClient client, ProviderConfiguration config = null) { _client = client; var logConfig = (config?.LoggingConfigurationFactory ?? Components.Logging()) - .CreateLoggingConfiguration(); + .Build(null); // If there is a base name for the logger, then use the namespace as the name. var log = logConfig.LogAdapter.Logger(logConfig.BaseLoggerName != null @@ -56,31 +57,31 @@ public Provider(ILdClient client, ProviderConfiguration config = null) /// public override Task> ResolveBooleanValue(string flagKey, bool defaultValue, EvaluationContext context = null) => Task.FromResult(_client - .BoolVariationDetail(flagKey, _contextConverter.ToLdUser(context), defaultValue) + .BoolVariationDetail(flagKey, _contextConverter.ToLdContext(context), defaultValue) .ToResolutionDetails(flagKey)); /// public override Task> ResolveStringValue(string flagKey, string defaultValue, EvaluationContext context = null) => Task.FromResult(_client - .StringVariationDetail(flagKey, _contextConverter.ToLdUser(context), defaultValue) + .StringVariationDetail(flagKey, _contextConverter.ToLdContext(context), defaultValue) .ToResolutionDetails(flagKey)); /// public override Task> ResolveIntegerValue(string flagKey, int defaultValue, EvaluationContext context = null) => Task.FromResult(_client - .IntVariationDetail(flagKey, _contextConverter.ToLdUser(context), defaultValue) + .IntVariationDetail(flagKey, _contextConverter.ToLdContext(context), defaultValue) .ToResolutionDetails(flagKey)); /// public override Task> ResolveDoubleValue(string flagKey, double defaultValue, EvaluationContext context = null) => Task.FromResult(_client - .DoubleVariationDetail(flagKey, _contextConverter.ToLdUser(context), defaultValue) + .DoubleVariationDetail(flagKey, _contextConverter.ToLdContext(context), defaultValue) .ToResolutionDetails(flagKey)); /// public override Task> ResolveStructureValue(string flagKey, Value defaultValue, EvaluationContext context = null) => Task.FromResult(_client - .JsonVariationDetail(flagKey, _contextConverter.ToLdUser(context), LdValue.Null) + .JsonVariationDetail(flagKey, _contextConverter.ToLdContext(context), LdValue.Null) .ToValueDetail(defaultValue).ToResolutionDetails(flagKey)); #endregion diff --git a/src/LaunchDarkly.OpenFeature.ServerProvider/ProviderConfiguration.cs b/src/LaunchDarkly.OpenFeature.ServerProvider/ProviderConfiguration.cs index e535456..54ce120 100644 --- a/src/LaunchDarkly.OpenFeature.ServerProvider/ProviderConfiguration.cs +++ b/src/LaunchDarkly.OpenFeature.ServerProvider/ProviderConfiguration.cs @@ -1,4 +1,5 @@ using LaunchDarkly.Sdk.Server.Interfaces; +using LaunchDarkly.Sdk.Server.Subsystems; namespace LaunchDarkly.OpenFeature.ServerProvider { @@ -62,7 +63,7 @@ public static ProviderConfigurationBuilder Builder(ProviderConfiguration fromCon /// SDK components should not use this property directly; instead, the SDK client will use it to create a /// logger instance which will be in . /// - public ILoggingConfigurationFactory LoggingConfigurationFactory { get; } + public IComponentConfigurer LoggingConfigurationFactory { get; } #region Internal constructor diff --git a/src/LaunchDarkly.OpenFeature.ServerProvider/ProviderConfigurationBuilder.cs b/src/LaunchDarkly.OpenFeature.ServerProvider/ProviderConfigurationBuilder.cs index 2622fb8..df8fbf7 100644 --- a/src/LaunchDarkly.OpenFeature.ServerProvider/ProviderConfigurationBuilder.cs +++ b/src/LaunchDarkly.OpenFeature.ServerProvider/ProviderConfigurationBuilder.cs @@ -1,6 +1,6 @@ using LaunchDarkly.Logging; using LaunchDarkly.Sdk.Server; -using LaunchDarkly.Sdk.Server.Interfaces; +using LaunchDarkly.Sdk.Server.Subsystems; namespace LaunchDarkly.OpenFeature.ServerProvider { @@ -26,7 +26,7 @@ public sealed class ProviderConfigurationBuilder // Rider/ReSharper would prefer these not use the private naming convention. // ReSharper disable once InconsistentNaming - internal ILoggingConfigurationFactory _loggingConfigurationFactory; + internal IComponentConfigurer _loggingConfigurationFactory; #endregion @@ -70,7 +70,7 @@ internal ProviderConfigurationBuilder(ProviderConfiguration fromConfiguration) /// /// /// - public ProviderConfigurationBuilder Logging(ILoggingConfigurationFactory loggingConfigurationFactory) + public ProviderConfigurationBuilder Logging(IComponentConfigurer loggingConfigurationFactory) { _loggingConfigurationFactory = loggingConfigurationFactory; return this; diff --git a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/EvalContextConverterTests.cs b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/EvalContextConverterTests.cs index 32801de..5e59077 100644 --- a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/EvalContextConverterTests.cs +++ b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/EvalContextConverterTests.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using LaunchDarkly.Logging; using LaunchDarkly.Sdk; using LaunchDarkly.Sdk.Server; @@ -22,7 +23,6 @@ public void ItCanHandleBuiltInAttributes() { var evaluationContext = EvaluationContext.Builder() .Set("targetingKey", "the-key") - .Set("secondary", "secondary") .Set("name", "name") .Set("firstName", "firstName") .Set("lastName", "lastName") @@ -32,10 +32,9 @@ public void ItCanHandleBuiltInAttributes() .Set("country", "country") .Set("anonymous", true).Build(); - var convertedUser = _converter.ToLdUser(evaluationContext); + var convertedUser = _converter.ToLdContext(evaluationContext); var expectedUser = User.Builder("the-key") - .Secondary("secondary") .Name("name") .FirstName("firstName") .LastName("lastName") @@ -46,7 +45,7 @@ public void ItCanHandleBuiltInAttributes() .Anonymous(true) .Build(); - Assert.Equal(expectedUser, convertedUser); + Assert.Equal(Context.FromUser(expectedUser), convertedUser); // Nothing is wrong with this, so it shouldn't have produced any messages. Assert.Empty(_logCapture.GetMessages()); } @@ -56,7 +55,6 @@ public void ItAllowsNullForBuiltInAttributes() { var evaluationContext = EvaluationContext.Builder() .Set("targetingKey", "the-key") - .Set("secondary", (string) null) .Set("name", (string) null) .Set("firstName", (string) null) .Set("lastName", (string) null) @@ -69,12 +67,12 @@ public void ItAllowsNullForBuiltInAttributes() .Set("anonymous", new Value()) .Build(); - var convertedUser = _converter.ToLdUser(evaluationContext); + var convertedUser = _converter.ToLdContext(evaluationContext); var expectedUser = User.Builder("the-key") .Build(); - Assert.Equal(expectedUser, convertedUser); + Assert.Equal(Context.FromUser(expectedUser), convertedUser); // Nothing is wrong with this, so it shouldn't have produced any messages. Assert.Empty(_logCapture.GetMessages()); } @@ -82,7 +80,7 @@ public void ItAllowsNullForBuiltInAttributes() [Fact] public void ItLogsAndErrorWhenThereIsNoTargetingKey() { - _converter.ToLdUser(EvaluationContext.Empty); + _converter.ToLdContext(EvaluationContext.Empty); Assert.True(_logCapture.HasMessageWithText(LogLevel.Error, "The EvaluationContext must contain either a 'targetingKey' or a 'key' and the type" + "must be a string.")); @@ -91,21 +89,14 @@ public void ItLogsAndErrorWhenThereIsNoTargetingKey() [Fact] public void ItLogsAWarningWhenBothTargetingKeyAndKeyAreDefined() { - _converter.ToLdUser(EvaluationContext.Builder().Set("targetingKey", "key").Set("key", "key").Build()); + _converter.ToLdContext(EvaluationContext.Builder().Set("targetingKey", "key").Set("key", "key").Build()); Assert.True(_logCapture.HasMessageWithText(LogLevel.Warn, "The EvaluationContext contained both a 'targetingKey' and a 'key' attribute. The 'key'" + "attribute will be discarded.")); } [Theory] - [InlineData("secondary", "string")] [InlineData("name", "string")] - [InlineData("firstName", "string")] - [InlineData("lastName", "string")] - [InlineData("email", "string")] - [InlineData("avatar", "string")] - [InlineData("ip", "string")] - [InlineData("country", "string")] [InlineData("anonymous", "bool")] public void ItLogsErrorsWhenTypesAreIncorrectForBuiltInAttributes(string attr, string type) { @@ -114,11 +105,11 @@ public void ItLogsErrorsWhenTypesAreIncorrectForBuiltInAttributes(string attr, s //Number isn't valid for any built-in, .Set(attr, 1).Build(); - _converter.ToLdUser(evaluationContext); + _converter.ToLdContext(evaluationContext); Assert.True(_logCapture.HasMessageWithRegex(LogLevel.Error, $".*attribute '{attr}'.*type {type}.*")); - _converter.ToLdUser(evaluationContext); + _converter.ToLdContext(evaluationContext); } [Fact] @@ -131,10 +122,10 @@ public void ItSupportsCustomAttributes() .Set(attributeKey, attributeValue) .Build(); - var ldUser = _converter.ToLdUser(evaluationContext); + var ldUser = _converter.ToLdContext(evaluationContext); Assert.Equal( attributeValue, - ldUser.GetAttribute(UserAttribute.ForName(attributeKey)).AsString + ldUser.GetValue(attributeKey).AsString ); } @@ -144,7 +135,7 @@ public void ItCanUseKeyAttribute() var evaluationContext = EvaluationContext.Builder() .Set("key", "the-key") .Build(); - Assert.Equal("the-key", _converter.ToLdUser(evaluationContext).Key); + Assert.Equal("the-key", _converter.ToLdContext(evaluationContext).Key); } [Fact] @@ -154,7 +145,61 @@ public void ItUsesTheTargetingKeyInFavorOfKey() .Set("key", "key") .Set("targetingKey", "targeting-key") .Build(); - Assert.Equal("targeting-key", _converter.ToLdUser(evaluationContext).Key); + Assert.Equal("targeting-key", _converter.ToLdContext(evaluationContext).Key); + } + + [Fact] + public void ItCanBuildASingleContext() + { + var evaluationContext = EvaluationContext.Builder() + .Set("kind", "organization") + .Set("name", "the-org-name") + .Set("targetingKey", "my-org-key") + .Set("anonymous", true) + .Set("myCustomAttribute", "myCustomValue") + .Set("privateAttributes", new Value(new List{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"}) + .Build(); + + Assert.Equal(expectedContext, _converter.ToLdContext(evaluationContext)); + } + + [Fact] + public void ItCanBuildAMultiContext() + { + var evaluationContext = EvaluationContext.Builder() + .Set("kind", "multi") + .Set("organization", new Structure(new Dictionary + { + {"targetingKey", new Value("my-org-key")}, + {"name", new Value("the-org-name")}, + {"myCustomAttribute", new Value("myAttributeValue")}, + {"privateAttributes", new Value(new List{new Value("myCustomAttribute")})} + })) + .Set("user", new Structure(new Dictionary { + {"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"}) + .Build()) + .Add(Context.Builder("my-user-key") + .Anonymous(true) + .Build()) + .Build(); + + Assert.Equal(expectedContext, _converter.ToLdContext(evaluationContext)); } } } diff --git a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/LaunchDarkly.OpenFeature.ServerProvider.Tests.csproj b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/LaunchDarkly.OpenFeature.ServerProvider.Tests.csproj index 78e2c29..73722b9 100644 --- a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/LaunchDarkly.OpenFeature.ServerProvider.Tests.csproj +++ b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/LaunchDarkly.OpenFeature.ServerProvider.Tests.csproj @@ -29,8 +29,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + diff --git a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/ProviderConfigurationTests.cs b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/ProviderConfigurationTests.cs index e1489cd..f39c5e0 100644 --- a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/ProviderConfigurationTests.cs +++ b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/ProviderConfigurationTests.cs @@ -25,7 +25,7 @@ public void LoggingAdapterShortcut() { var adapter = Logs.ToWriter(Console.Out); var config = ProviderConfiguration.Builder().Logging(adapter).Build(); - var logConfig = config.LoggingConfigurationFactory.CreateLoggingConfiguration(); + var logConfig = config.LoggingConfigurationFactory.Build(null); Assert.Same(adapter, logConfig.LogAdapter); } } diff --git a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/ProviderTests.cs b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/ProviderTests.cs index 5f895d6..b85827f 100644 --- a/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/ProviderTests.cs +++ b/test/LaunchDarkly.OpenFeature.ServerProvider.Tests/ProviderTests.cs @@ -12,7 +12,7 @@ namespace LaunchDarkly.OpenFeature.ServerProvider.Tests public class ProviderTests { private readonly EvalContextConverter _converter = - new EvalContextConverter(Components.NoLogging.CreateLoggingConfiguration().LogAdapter.Logger("test")); + new EvalContextConverter(Components.NoLogging.Build(null).LogAdapter.Logger("test")); [Fact] public void ItCanProvideMetaData() @@ -74,7 +74,7 @@ public void ItCanDoABooleanEvaluation() .Build(); var mock = new Mock(); mock.Setup(l => l.BoolVariationDetail("flag-key", - _converter.ToLdUser(evaluationContext), false)) + _converter.ToLdContext(evaluationContext), false)) .Returns(new EvaluationDetail(true, 10, EvaluationReason.FallthroughReason)); var provider = new Provider(mock.Object); @@ -90,7 +90,7 @@ public void ItCanDoAStringEvaluation() .Build(); var mock = new Mock(); mock.Setup(l => l.StringVariationDetail("flag-key", - _converter.ToLdUser(evaluationContext), "default")) + _converter.ToLdContext(evaluationContext), "default")) .Returns(new EvaluationDetail("notDefault", 10, EvaluationReason.FallthroughReason)); var provider = new Provider(mock.Object); @@ -106,7 +106,7 @@ public void ItCanDoAnIntegerEvaluation() .Build(); var mock = new Mock(); mock.Setup(l => l.IntVariationDetail("flag-key", - _converter.ToLdUser(evaluationContext), 0)) + _converter.ToLdContext(evaluationContext), 0)) .Returns(new EvaluationDetail(1, 10, EvaluationReason.FallthroughReason)); var provider = new Provider(mock.Object); @@ -122,7 +122,7 @@ public void ItCanDoADoubleEvaluation() .Build(); var mock = new Mock(); mock.Setup(l => l.DoubleVariationDetail("flag-key", - _converter.ToLdUser(evaluationContext), 0)) + _converter.ToLdContext(evaluationContext), 0)) .Returns(new EvaluationDetail(1.7, 10, EvaluationReason.FallthroughReason)); var provider = new Provider(mock.Object); @@ -138,7 +138,7 @@ public void ItCanDoAValueEvaluation() .Build(); var mock = new Mock(); mock.Setup(l => l.JsonVariationDetail("flag-key", - It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny())) .Returns(new EvaluationDetail(LdValue.Of("true"), 10, EvaluationReason.FallthroughReason)); var provider = new Provider(mock.Object);