From 2c42d1fc82587493bd333004b9618797953739f6 Mon Sep 17 00:00:00 2001 From: Artyom Tonoyan Date: Mon, 29 Sep 2025 21:41:23 +0400 Subject: [PATCH 1/9] Add OpenFeature.DependencyInjection.Abstractions project Introduced a new project, `OpenFeature.DependencyInjection.Abstractions`, to the solution. This project supports dependency injection abstractions and targets multiple frameworks (`netstandard2.0`, `net8.0`, `net9.0`, `net462`) for broad compatibility. Configured the project with the `Microsoft.NET.Sdk` SDK and set the root namespace to `OpenFeature.DependencyInjection.Abstractions`. Added dependencies on `Microsoft.Extensions.DependencyInjection.Abstractions` and `Microsoft.Extensions.Options` to enable DI and options configuration. --- OpenFeature.slnx | 1 + ...nFeature.DependencyInjection.Abstractions.csproj | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj diff --git a/OpenFeature.slnx b/OpenFeature.slnx index 28b31d340..7a386eaf4 100644 --- a/OpenFeature.slnx +++ b/OpenFeature.slnx @@ -51,6 +51,7 @@ + diff --git a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj new file mode 100644 index 000000000..ddccda15e --- /dev/null +++ b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0;net8.0;net9.0;net462 + OpenFeature.DependencyInjection.Abstractions + + + + + + + + From 4d69c6f6637718c180383c5268b23690c6c7c8ac Mon Sep 17 00:00:00 2001 From: Artyom Tonoyan Date: Sun, 5 Oct 2025 00:58:08 +0400 Subject: [PATCH 2/9] Refactor OpenFeature for DI and provider abstraction Refactored the OpenFeature framework to introduce `OpenFeatureProviderBuilder`, enhancing support for dependency injection and provider management. - Changed namespaces to align with DI abstractions. - Made `FeatureCodes` public for broader accessibility. - Added `InternalsVisibleTo` for testing and project references. - Introduced `OpenFeatureProviderBuilder` for managing providers and policies. - Added extension methods for provider and policy registration. - Refactored `OpenFeatureBuilder` to inherit from `OpenFeatureProviderBuilder`. - Consolidated shared functionality in `OpenFeatureProviderOptions`. - Updated `FeatureBuilderExtensions` and `InMemoryProviderOptions` to use the new abstraction. - Updated tests to reflect new method signatures and hierarchy. - Removed redundant methods and properties, improving code organization. These changes improve maintainability, extensibility, and alignment with modern DI patterns. --- .../Diagnostics/FeatureCodes.cs | 4 +- ...re.DependencyInjection.Abstractions.csproj | 5 + .../OpenFeatureProviderBuilder.cs | 62 ++++++ .../OpenFeatureProviderBuilderExtensions.cs | 153 ++++++++++++++ .../OpenFeatureProviderOptions.cs | 61 ++++++ .../PolicyNameOptions.cs | 2 +- .../OpenFeature.Hosting.csproj | 1 + src/OpenFeature.Hosting/OpenFeatureBuilder.cs | 90 ++++++--- .../OpenFeatureBuilderExtensions.cs | 188 +----------------- src/OpenFeature.Hosting/OpenFeatureOptions.cs | 46 +---- .../Memory/FeatureBuilderExtensions.cs | 53 ++--- .../Memory/InMemoryProviderOptions.cs | 3 +- .../OpenFeatureBuilderExtensionsTests.cs | 37 ++-- 13 files changed, 399 insertions(+), 306 deletions(-) rename src/{OpenFeature.Hosting => OpenFeature.DependencyInjection.Abstractions}/Diagnostics/FeatureCodes.cs (93%) create mode 100644 src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilder.cs create mode 100644 src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilderExtensions.cs create mode 100644 src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderOptions.cs rename src/{OpenFeature.Hosting => OpenFeature.DependencyInjection.Abstractions}/PolicyNameOptions.cs (84%) diff --git a/src/OpenFeature.Hosting/Diagnostics/FeatureCodes.cs b/src/OpenFeature.DependencyInjection.Abstractions/Diagnostics/FeatureCodes.cs similarity index 93% rename from src/OpenFeature.Hosting/Diagnostics/FeatureCodes.cs rename to src/OpenFeature.DependencyInjection.Abstractions/Diagnostics/FeatureCodes.cs index f7ecf81cb..e24b83c66 100644 --- a/src/OpenFeature.Hosting/Diagnostics/FeatureCodes.cs +++ b/src/OpenFeature.DependencyInjection.Abstractions/Diagnostics/FeatureCodes.cs @@ -1,4 +1,4 @@ -namespace OpenFeature.Hosting.Diagnostics; +namespace OpenFeature.DependencyInjection.Abstractions.Diagnostics; /// /// Contains identifiers for experimental features and diagnostics in the OpenFeature framework. @@ -22,7 +22,7 @@ namespace OpenFeature.Hosting.Diagnostics; /// - "001" - Unique identifier for a specific feature. /// /// -internal static class FeatureCodes +public static class FeatureCodes { /// /// Identifier for the experimental Dependency Injection features within the OpenFeature framework. diff --git a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj index ddccda15e..e39768703 100644 --- a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj +++ b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj @@ -10,4 +10,9 @@ + + + + + diff --git a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilder.cs b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilder.cs new file mode 100644 index 000000000..5ff17f47d --- /dev/null +++ b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilder.cs @@ -0,0 +1,62 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace OpenFeature.DependencyInjection.Abstractions; + +/// +/// Describes a backed by an . +/// +public abstract class OpenFeatureProviderBuilder(IServiceCollection services) +{ + /// The services being configured. + public IServiceCollection Services { get; } = services; + + /// + /// Gets a value indicating whether a default provider has been registered. + /// + public bool HasDefaultProvider { get; internal set; } + + /// + /// Gets the count of domain-bound providers that have been registered. + /// This count does not include the default provider. + /// + public int DomainBoundProviderRegistrationCount { get; internal set; } + + /// + /// Indicates whether the policy has been configured. + /// + public bool IsPolicyConfigured { get; internal set; } + + /// + /// Validates the current configuration, ensuring that a policy is set when multiple providers are registered + /// or when a default provider is registered alongside another provider. + /// + /// + /// Thrown if multiple providers are registered without a policy, or if both a default provider + /// and an additional provider are registered without a policy configuration. + /// + public void Validate() + { + if (IsPolicyConfigured) + { + return; + } + + if (DomainBoundProviderRegistrationCount > 1) + { + throw new InvalidOperationException("Multiple providers have been registered, but no policy has been configured."); + } + + if (HasDefaultProvider && DomainBoundProviderRegistrationCount == 1) + { + throw new InvalidOperationException("A default provider and an additional provider have been registered without a policy configuration."); + } + } + + /// + /// Adds an IFeatureClient to the container. If is supplied, + /// registers a domain-bound client; otherwise registers a global client. If an evaluation context is + /// configured, it is applied at resolve-time. + /// + /// The current . + internal protected abstract OpenFeatureProviderBuilder TryAddClient(string? name = null); +} diff --git a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilderExtensions.cs b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilderExtensions.cs new file mode 100644 index 000000000..4fd76aa3d --- /dev/null +++ b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilderExtensions.cs @@ -0,0 +1,153 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using OpenFeature.DependencyInjection.Abstractions; + +namespace OpenFeature; + +/// +/// Contains extension methods for the class. +/// +#if NET8_0_OR_GREATER +[System.Diagnostics.CodeAnalysis.Experimental(DependencyInjection.Abstractions.Diagnostics.FeatureCodes.NewDi)] +#endif +public static partial class OpenFeatureProviderBuilderExtensions +{ + /// + /// Adds a feature provider using a factory method without additional configuration options. + /// This method adds the feature provider as a transient service and sets it as the default provider within the application. + /// + /// The concrete feature provider type to register as the default. + /// The used to configure feature flags. + /// + /// A factory method that creates and returns a + /// instance based on the provided service provider. + /// + /// The updated instance with the default feature provider set and configured. + /// Thrown if the is null, as a valid builder is required to add and configure providers. + public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, Func implementationFactory) + where TFeatureProvider : class + => AddProvider(builder, implementationFactory, null); + + /// + /// Adds a feature provider using a factory method to create the provider instance and optionally configures its settings. + /// This method adds the feature provider as a transient service and sets it as the default provider within the application. + /// + /// The concrete feature provider type to register as the default. + /// Type derived from used to configure the feature provider. + /// The used to configure feature flags. + /// + /// A factory method that creates and returns a + /// instance based on the provided service provider. + /// + /// An optional delegate to configure the provider-specific options. + /// The updated instance with the default feature provider set and configured. + /// Thrown if the is null, as a valid builder is required to add and configure providers. + public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, Func implementationFactory, Action? configureOptions) + where TFeatureProvider : class + where TOptions : OpenFeatureProviderOptions + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + builder.HasDefaultProvider = true; + builder.Services.PostConfigure(options => options.AddDefaultProviderName()); + if (configureOptions != null) + { + builder.Services.Configure(configureOptions); + } + + builder.Services.TryAddTransient(implementationFactory); + builder.TryAddClient(); + return builder; + } + + /// + /// Adds a feature provider for a specific domain using provided options and a configuration builder. + /// + /// The concrete feature provider type to register as the default. + /// Type derived from used to configure the feature provider. + /// The used to configure feature flags. + /// The unique name of the provider. + /// + /// A factory method that creates a feature provider instance. + /// It adds the provider as a transient service unless it is already added. + /// + /// An optional delegate to configure the provider-specific options. + /// The updated instance with the new feature provider configured. + /// + /// Thrown if either or is null or if the is empty. + /// + public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, string domain, Func implementationFactory, Action? configureOptions) + where TFeatureProvider : class + where TOptions : OpenFeatureProviderOptions + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + builder.DomainBoundProviderRegistrationCount++; + + builder.Services.PostConfigure(options => options.AddProviderName(domain)); + if (configureOptions != null) + { + builder.Services.Configure(domain, configureOptions); + } + + builder.Services.TryAddKeyedTransient(domain, (provider, key) => + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + return implementationFactory(provider, key.ToString()!); + }); + + builder.TryAddClient(domain); + return builder; + } + + /// + /// Adds a feature provider for a specified domain using the default options. + /// This method configures a feature provider without custom options, delegating to the more generic AddProvider method. + /// + /// The concrete feature provider type to register as the default. + /// The used to configure feature flags. + /// The unique name of the provider. + /// + /// A factory method that creates a feature provider instance. + /// It adds the provider as a transient service unless it is already added. + /// + /// The updated instance with the new feature provider configured. + /// + /// Thrown if either or is null or if the is empty. + /// + public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, string domain, Func implementationFactory) + where TFeatureProvider : class + => AddProvider(builder, domain, implementationFactory, configureOptions: null); + + /// + /// Configures policy name options for OpenFeature using the specified options type. + /// + /// The type of options used to configure . + /// The instance. + /// A delegate to configure . + /// The configured instance. + /// Thrown when the or is null. + public static OpenFeatureProviderBuilder AddPolicyName(this OpenFeatureProviderBuilder builder, Action configureOptions) + where TOptions : PolicyNameOptions + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (configureOptions == null) throw new ArgumentNullException(nameof(configureOptions)); + + builder.IsPolicyConfigured = true; + + builder.Services.Configure(configureOptions); + return builder; + } + + /// + /// Configures the default policy name options for OpenFeature. + /// + /// The instance. + /// A delegate to configure . + /// The configured instance. + public static OpenFeatureProviderBuilder AddPolicyName(this OpenFeatureProviderBuilder builder, Action configureOptions) + => AddPolicyName(builder, configureOptions); +} diff --git a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderOptions.cs b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderOptions.cs new file mode 100644 index 000000000..218204dfe --- /dev/null +++ b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderOptions.cs @@ -0,0 +1,61 @@ +using System.Collections.ObjectModel; + +namespace OpenFeature.DependencyInjection.Abstractions; + +/// +/// Provider-focused options for configuring OpenFeature integrations. +/// Contains only contracts and metadata that integrations may need. +/// +public class OpenFeatureProviderOptions +{ + private readonly HashSet _providerNames = []; + + /// + /// Determines if a default provider has been registered. + /// + public bool HasDefaultProvider { get; private set; } + + /// + /// The of the configured feature provider, if any. + /// Typically set by higher-level configuration. + /// + public Type FeatureProviderType { get; protected internal set; } = null!; + + /// + /// Gets a read-only list of registered provider names. + /// + public IReadOnlyCollection ProviderNames + { + get + { + lock (_providerNames) + { + return new ReadOnlyCollection([.. _providerNames]); + } + } + } + + /// + /// Registers the default provider name if no specific name is provided. + /// Sets to true. + /// + internal void AddDefaultProviderName() => AddProviderName(null); + + /// + /// Registers a new feature provider name. This operation is thread-safe. + /// + /// The name of the feature provider to register. Registers as default if null. + internal void AddProviderName(string? name) + { + if (string.IsNullOrWhiteSpace(name)) + { + HasDefaultProvider = true; + return; + } + + lock (_providerNames) + { + _providerNames.Add(name!); + } + } +} diff --git a/src/OpenFeature.Hosting/PolicyNameOptions.cs b/src/OpenFeature.DependencyInjection.Abstractions/PolicyNameOptions.cs similarity index 84% rename from src/OpenFeature.Hosting/PolicyNameOptions.cs rename to src/OpenFeature.DependencyInjection.Abstractions/PolicyNameOptions.cs index 3dfa76f89..8ea167e43 100644 --- a/src/OpenFeature.Hosting/PolicyNameOptions.cs +++ b/src/OpenFeature.DependencyInjection.Abstractions/PolicyNameOptions.cs @@ -1,4 +1,4 @@ -namespace OpenFeature.Hosting; +namespace OpenFeature.DependencyInjection.Abstractions; /// /// Options to configure the default feature client name. diff --git a/src/OpenFeature.Hosting/OpenFeature.Hosting.csproj b/src/OpenFeature.Hosting/OpenFeature.Hosting.csproj index 85131a0fa..b067c1e7f 100644 --- a/src/OpenFeature.Hosting/OpenFeature.Hosting.csproj +++ b/src/OpenFeature.Hosting/OpenFeature.Hosting.csproj @@ -10,6 +10,7 @@ + diff --git a/src/OpenFeature.Hosting/OpenFeatureBuilder.cs b/src/OpenFeature.Hosting/OpenFeatureBuilder.cs index 177a9fac3..aeaee6073 100644 --- a/src/OpenFeature.Hosting/OpenFeatureBuilder.cs +++ b/src/OpenFeature.Hosting/OpenFeatureBuilder.cs @@ -1,4 +1,7 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using OpenFeature.DependencyInjection.Abstractions; +using OpenFeature.Model; namespace OpenFeature.Hosting; @@ -6,11 +9,8 @@ namespace OpenFeature.Hosting; /// Describes a backed by an . /// /// The services being configured. -public class OpenFeatureBuilder(IServiceCollection services) +public class OpenFeatureBuilder(IServiceCollection services) : OpenFeatureProviderBuilder(services) { - /// The services being configured. - public IServiceCollection Services { get; } = services; - /// /// Indicates whether the evaluation context has been configured. /// This property is used to determine if specific configurations or services @@ -19,42 +19,76 @@ public class OpenFeatureBuilder(IServiceCollection services) public bool IsContextConfigured { get; internal set; } /// - /// Indicates whether the policy has been configured. - /// - public bool IsPolicyConfigured { get; internal set; } - - /// - /// Gets a value indicating whether a default provider has been registered. + /// Internal convenience API to add a client by name (or the default client when is null/empty). + /// Delegates to the overridable . /// - public bool HasDefaultProvider { get; internal set; } + /// Optional key for a named client registration. + /// The current . + internal OpenFeatureProviderBuilder AddClient(string? name = null) + => TryAddClient(name); /// - /// Gets the count of domain-bound providers that have been registered. - /// This count does not include the default provider. + /// Adds an to the container, optionally keyed by . + /// If an evaluation context is configured, the client is created with that context. /// - public int DomainBoundProviderRegistrationCount { get; internal set; } + /// Optional key for a named client registration. + /// The current . + protected override OpenFeatureProviderBuilder TryAddClient(string? name = null) + => string.IsNullOrWhiteSpace(name) ? AddClient() : AddDomainBoundClient(name!); /// - /// Validates the current configuration, ensuring that a policy is set when multiple providers are registered - /// or when a default provider is registered alongside another provider. + /// Adds a global (domain-agnostic) . + /// The evaluation context (if configured) is resolved per scope at resolve-time. /// - /// - /// Thrown if multiple providers are registered without a policy, or if both a default provider - /// and an additional provider are registered without a policy configuration. - /// - public void Validate() + /// The current . + private OpenFeatureProviderBuilder AddClient() { - if (!IsPolicyConfigured) + if (IsContextConfigured) { - if (DomainBoundProviderRegistrationCount > 1) + Services.TryAddScoped(static provider => { - throw new InvalidOperationException("Multiple providers have been registered, but no policy has been configured."); - } + var api = provider.GetRequiredService(); + var client = api.GetClient(); + var context = provider.GetRequiredService(); + client.SetContext(context); + return client; + }); + } + else + { + Services.TryAddScoped(static provider => + { + var api = provider.GetRequiredService(); + return api.GetClient(); + }); + } - if (HasDefaultProvider && DomainBoundProviderRegistrationCount == 1) + return this; + } + + /// + private OpenFeatureProviderBuilder AddDomainBoundClient(string name) + { + if (IsContextConfigured) + { + Services.TryAddKeyedScoped(name, static (provider, key) => { - throw new InvalidOperationException("A default provider and an additional provider have been registered without a policy configuration."); - } + var api = provider.GetRequiredService(); + var client = api.GetClient(key!.ToString()); + var context = provider.GetRequiredService(); + client.SetContext(context); + return client; + }); } + else + { + Services.TryAddKeyedScoped(name, static (provider, key) => + { + var api = provider.GetRequiredService(); + return api.GetClient(key!.ToString()); + }); + } + + return this; } } diff --git a/src/OpenFeature.Hosting/OpenFeatureBuilderExtensions.cs b/src/OpenFeature.Hosting/OpenFeatureBuilderExtensions.cs index 52c66c42e..6714ecf61 100644 --- a/src/OpenFeature.Hosting/OpenFeatureBuilderExtensions.cs +++ b/src/OpenFeature.Hosting/OpenFeatureBuilderExtensions.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using OpenFeature.Constant; +using OpenFeature.DependencyInjection.Abstractions; using OpenFeature.Hosting; using OpenFeature.Hosting.Internal; using OpenFeature.Model; @@ -51,164 +52,6 @@ public static OpenFeatureBuilder AddContext(this OpenFeatureBuilder builder, Act return builder; } - /// - /// Adds a feature provider using a factory method without additional configuration options. - /// This method adds the feature provider as a transient service and sets it as the default provider within the application. - /// - /// The used to configure feature flags. - /// - /// A factory method that creates and returns a - /// instance based on the provided service provider. - /// - /// The updated instance with the default feature provider set and configured. - /// Thrown if the is null, as a valid builder is required to add and configure providers. - public static OpenFeatureBuilder AddProvider(this OpenFeatureBuilder builder, Func implementationFactory) - => AddProvider(builder, implementationFactory, null); - - /// - /// Adds a feature provider using a factory method to create the provider instance and optionally configures its settings. - /// This method adds the feature provider as a transient service and sets it as the default provider within the application. - /// - /// Type derived from used to configure the feature provider. - /// The used to configure feature flags. - /// - /// A factory method that creates and returns a - /// instance based on the provided service provider. - /// - /// An optional delegate to configure the provider-specific options. - /// The updated instance with the default feature provider set and configured. - /// Thrown if the is null, as a valid builder is required to add and configure providers. - public static OpenFeatureBuilder AddProvider(this OpenFeatureBuilder builder, Func implementationFactory, Action? configureOptions) - where TOptions : OpenFeatureOptions - { - Guard.ThrowIfNull(builder); - - builder.HasDefaultProvider = true; - builder.Services.PostConfigure(options => options.AddDefaultProviderName()); - if (configureOptions != null) - { - builder.Services.Configure(configureOptions); - } - - builder.Services.TryAddTransient(implementationFactory); - builder.AddClient(); - return builder; - } - - /// - /// Adds a feature provider for a specific domain using provided options and a configuration builder. - /// - /// Type derived from used to configure the feature provider. - /// The used to configure feature flags. - /// The unique name of the provider. - /// - /// A factory method that creates a feature provider instance. - /// It adds the provider as a transient service unless it is already added. - /// - /// An optional delegate to configure the provider-specific options. - /// The updated instance with the new feature provider configured. - /// - /// Thrown if either or is null or if the is empty. - /// - public static OpenFeatureBuilder AddProvider(this OpenFeatureBuilder builder, string domain, Func implementationFactory, Action? configureOptions) - where TOptions : OpenFeatureOptions - { - Guard.ThrowIfNull(builder); - - builder.DomainBoundProviderRegistrationCount++; - - builder.Services.PostConfigure(options => options.AddProviderName(domain)); - if (configureOptions != null) - { - builder.Services.Configure(domain, configureOptions); - } - - builder.Services.TryAddKeyedTransient(domain, (provider, key) => - { - if (key == null) - { - throw new ArgumentNullException(nameof(key)); - } - return implementationFactory(provider, key.ToString()!); - }); - - builder.AddClient(domain); - return builder; - } - - /// - /// Adds a feature provider for a specified domain using the default options. - /// This method configures a feature provider without custom options, delegating to the more generic AddProvider method. - /// - /// The used to configure feature flags. - /// The unique name of the provider. - /// - /// A factory method that creates a feature provider instance. - /// It adds the provider as a transient service unless it is already added. - /// - /// The updated instance with the new feature provider configured. - /// - /// Thrown if either or is null or if the is empty. - /// - public static OpenFeatureBuilder AddProvider(this OpenFeatureBuilder builder, string domain, Func implementationFactory) - => AddProvider(builder, domain, implementationFactory, configureOptions: null); - - /// - /// Adds a feature client to the service collection, configuring it to work with a specific context if provided. - /// - /// The instance. - /// Optional: The name for the feature client instance. - /// The instance. - internal static OpenFeatureBuilder AddClient(this OpenFeatureBuilder builder, string? name = null) - { - if (string.IsNullOrWhiteSpace(name)) - { - if (builder.IsContextConfigured) - { - builder.Services.TryAddScoped(static provider => - { - var api = provider.GetRequiredService(); - var client = api.GetClient(); - var context = provider.GetRequiredService(); - client.SetContext(context); - return client; - }); - } - else - { - builder.Services.TryAddScoped(static provider => - { - var api = provider.GetRequiredService(); - return api.GetClient(); - }); - } - } - else - { - if (builder.IsContextConfigured) - { - builder.Services.TryAddKeyedScoped(name, static (provider, key) => - { - var api = provider.GetRequiredService(); - var client = api.GetClient(key!.ToString()); - var context = provider.GetRequiredService(); - client.SetContext(context); - return client; - }); - } - else - { - builder.Services.TryAddKeyedScoped(name, static (provider, key) => - { - var api = provider.GetRequiredService(); - return api.GetClient(key!.ToString()); - }); - } - } - - return builder; - } - /// /// Adds a default to the based on the policy name options. /// This method configures the dependency injection container to resolve the appropriate @@ -233,35 +76,6 @@ internal static OpenFeatureBuilder AddPolicyBasedClient(this OpenFeatureBuilder return builder; } - /// - /// Configures policy name options for OpenFeature using the specified options type. - /// - /// The type of options used to configure . - /// The instance. - /// A delegate to configure . - /// The configured instance. - /// Thrown when the or is null. - public static OpenFeatureBuilder AddPolicyName(this OpenFeatureBuilder builder, Action configureOptions) - where TOptions : PolicyNameOptions - { - Guard.ThrowIfNull(builder); - Guard.ThrowIfNull(configureOptions); - - builder.IsPolicyConfigured = true; - - builder.Services.Configure(configureOptions); - return builder; - } - - /// - /// Configures the default policy name options for OpenFeature. - /// - /// The instance. - /// A delegate to configure . - /// The configured instance. - public static OpenFeatureBuilder AddPolicyName(this OpenFeatureBuilder builder, Action configureOptions) - => AddPolicyName(builder, configureOptions); - /// /// Adds a feature hook to the service collection using a factory method. Hooks added here are not domain-bound. /// diff --git a/src/OpenFeature.Hosting/OpenFeatureOptions.cs b/src/OpenFeature.Hosting/OpenFeatureOptions.cs index 9d3dd818e..de2cdc2c8 100644 --- a/src/OpenFeature.Hosting/OpenFeatureOptions.cs +++ b/src/OpenFeature.Hosting/OpenFeatureOptions.cs @@ -1,52 +1,12 @@ +using OpenFeature.DependencyInjection.Abstractions; + namespace OpenFeature.Hosting; /// /// Options to configure OpenFeature /// -public class OpenFeatureOptions +public class OpenFeatureOptions : OpenFeatureProviderOptions { - private readonly HashSet _providerNames = []; - - /// - /// Determines if a default provider has been registered. - /// - public bool HasDefaultProvider { get; private set; } - - /// - /// The type of the configured feature provider. - /// - public Type FeatureProviderType { get; protected internal set; } = null!; - - /// - /// Gets a read-only list of registered provider names. - /// - public IReadOnlyCollection ProviderNames => _providerNames; - - /// - /// Registers the default provider name if no specific name is provided. - /// Sets to true. - /// - protected internal void AddDefaultProviderName() => AddProviderName(null); - - /// - /// Registers a new feature provider name. This operation is thread-safe. - /// - /// The name of the feature provider to register. Registers as default if null. - protected internal void AddProviderName(string? name) - { - if (string.IsNullOrWhiteSpace(name)) - { - HasDefaultProvider = true; - } - else - { - lock (_providerNames) - { - _providerNames.Add(name!); - } - } - } - private readonly HashSet _hookNames = []; internal IReadOnlyCollection HookNames => _hookNames; diff --git a/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs b/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs index d63009d62..f7f1fb8ac 100644 --- a/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs +++ b/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs @@ -1,27 +1,28 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using OpenFeature.DependencyInjection.Abstractions; using OpenFeature.Providers.Memory; namespace OpenFeature.Hosting.Providers.Memory; /// -/// Extension methods for configuring feature providers with . +/// Extension methods for configuring feature providers with . /// #if NET8_0_OR_GREATER -[System.Diagnostics.CodeAnalysis.Experimental(Diagnostics.FeatureCodes.NewDi)] +[System.Diagnostics.CodeAnalysis.Experimental(DependencyInjection.Abstractions.Diagnostics.FeatureCodes.NewDi)] #endif public static partial class FeatureBuilderExtensions { /// - /// Adds an in-memory feature provider to the with a factory for flags. + /// Adds an in-memory feature provider to the with a factory for flags. /// - /// The instance to configure. + /// The instance to configure. /// /// A factory function to provide an of flags. /// If null, an empty provider will be created. /// - /// The instance for chaining. - public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder builder, Func?> flagsFactory) + /// The instance for chaining. + public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, Func?> flagsFactory) => builder.AddProvider(provider => { var flags = flagsFactory(provider); @@ -34,29 +35,29 @@ public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder bui }); /// - /// Adds an in-memory feature provider to the with a domain and factory for flags. + /// Adds an in-memory feature provider to the with a domain and factory for flags. /// - /// The instance to configure. + /// The instance to configure. /// The unique domain of the provider. /// /// A factory function to provide an of flags. /// If null, an empty provider will be created. /// - /// The instance for chaining. - public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder builder, string domain, Func?> flagsFactory) + /// The instance for chaining. + public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, string domain, Func?> flagsFactory) => builder.AddInMemoryProvider(domain, (provider, _) => flagsFactory(provider)); /// - /// Adds an in-memory feature provider to the with a domain and contextual flag factory. + /// Adds an in-memory feature provider to the with a domain and contextual flag factory. /// If null, an empty provider will be created. /// - /// The instance to configure. + /// The instance to configure. /// The unique domain of the provider. /// /// A factory function to provide an of flags based on service provider and domain. /// - /// The instance for chaining. - public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder builder, string domain, Func?> flagsFactory) + /// The instance for chaining. + public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, string domain, Func?> flagsFactory) => builder.AddProvider(domain, (provider, key) => { var flags = flagsFactory(provider, key); @@ -69,31 +70,31 @@ public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder bui }); /// - /// Adds an in-memory feature provider to the with optional flag configuration. + /// Adds an in-memory feature provider to the with optional flag configuration. /// - /// The instance to configure. + /// The instance to configure. /// /// An optional delegate to configure feature flags in the in-memory provider. /// If null, an empty provider will be created. /// - /// The instance for chaining. - public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder builder, Action>? configure = null) - => builder.AddProvider(CreateProvider, options => ConfigureFlags(options, configure)); + /// The instance for chaining. + public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, Action>? configure = null) + => builder.AddProvider(CreateProvider, options => ConfigureFlags(options, configure)); /// - /// Adds an in-memory feature provider with a specific domain to the with optional flag configuration. + /// Adds an in-memory feature provider with a specific domain to the with optional flag configuration. /// - /// The instance to configure. + /// The instance to configure. /// The unique domain of the provider /// /// An optional delegate to configure feature flags in the in-memory provider. /// If null, an empty provider will be created. /// - /// The instance for chaining. - public static OpenFeatureBuilder AddInMemoryProvider(this OpenFeatureBuilder builder, string domain, Action>? configure = null) - => builder.AddProvider(domain, CreateProvider, options => ConfigureFlags(options, configure)); + /// The instance for chaining. + public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, string domain, Action>? configure = null) + => builder.AddProvider(domain, CreateProvider, options => ConfigureFlags(options, configure)); - private static FeatureProvider CreateProvider(IServiceProvider provider, string domain) + private static InMemoryProvider CreateProvider(IServiceProvider provider, string domain) { var options = provider.GetRequiredService>().Get(domain); if (options.Flags == null) @@ -104,7 +105,7 @@ private static FeatureProvider CreateProvider(IServiceProvider provider, string return new InMemoryProvider(options.Flags); } - private static FeatureProvider CreateProvider(IServiceProvider provider) + private static InMemoryProvider CreateProvider(IServiceProvider provider) { var options = provider.GetRequiredService>().Value; if (options.Flags == null) diff --git a/src/OpenFeature.Hosting/Providers/Memory/InMemoryProviderOptions.cs b/src/OpenFeature.Hosting/Providers/Memory/InMemoryProviderOptions.cs index 3e7431eef..15310905c 100644 --- a/src/OpenFeature.Hosting/Providers/Memory/InMemoryProviderOptions.cs +++ b/src/OpenFeature.Hosting/Providers/Memory/InMemoryProviderOptions.cs @@ -1,3 +1,4 @@ +using OpenFeature.DependencyInjection.Abstractions; using OpenFeature.Providers.Memory; namespace OpenFeature.Hosting.Providers.Memory; @@ -5,7 +6,7 @@ namespace OpenFeature.Hosting.Providers.Memory; /// /// Options for configuring the in-memory feature flag provider. /// -public class InMemoryProviderOptions : OpenFeatureOptions +public class InMemoryProviderOptions : OpenFeatureProviderOptions { /// /// Gets or sets the feature flags to be used by the in-memory provider. diff --git a/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs b/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs index 1a284c918..414ae384a 100644 --- a/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs +++ b/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; +using OpenFeature.DependencyInjection.Abstractions; using OpenFeature.Hosting.Internal; using OpenFeature.Model; @@ -58,7 +59,7 @@ public void AddContext_Delegate_ShouldCorrectlyHandles(bool useServiceProviderDe } #if NET8_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.Experimental(Diagnostics.FeatureCodes.NewDi)] + [System.Diagnostics.CodeAnalysis.Experimental(DependencyInjection.Abstractions.Diagnostics.FeatureCodes.NewDi)] #endif [Theory] [InlineData(1, true, 0)] @@ -72,8 +73,8 @@ public void AddProvider_ShouldAddProviderToCollection(int providerRegistrationTy { 1 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider()), 2 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider()), - 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), - 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), + 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), + 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), _ => throw new InvalidOperationException("Invalid mode.") }; @@ -91,7 +92,7 @@ public void AddProvider_ShouldAddProviderToCollection(int providerRegistrationTy class TestOptions : OpenFeatureOptions { } #if NET8_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.Experimental(Diagnostics.FeatureCodes.NewDi)] + [System.Diagnostics.CodeAnalysis.Experimental(DependencyInjection.Abstractions.Diagnostics.FeatureCodes.NewDi)] #endif [Theory] [InlineData(1)] @@ -105,8 +106,8 @@ public void AddProvider_ShouldResolveCorrectProvider(int providerRegistrationTyp { 1 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider()), 2 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider()), - 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), - 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), + 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), + 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), _ => throw new InvalidOperationException("Invalid mode.") }; @@ -149,22 +150,22 @@ public void AddProvider_VerifiesDefaultAndDomainBoundProvidersBasedOnConfigurati .AddProvider("test1", (_, _) => new NoOpFeatureProvider()) .AddProvider("test2", (_, _) => new NoOpFeatureProvider()), 4 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) .AddProvider("test", (_, _) => new NoOpFeatureProvider()), 5 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) .AddProvider("test", (_, _) => new NoOpFeatureProvider()), 6 => _systemUnderTest - .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) .AddProvider("test2", (_, _) => new NoOpFeatureProvider()), 7 => _systemUnderTest .AddProvider(_ => new NoOpFeatureProvider()) .AddProvider("test", (_, _) => new NoOpFeatureProvider()) .AddProvider("test2", (_, _) => new NoOpFeatureProvider()), 8 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) - .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }) - .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }), + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }), _ => throw new InvalidOperationException("Invalid mode.") }; @@ -203,15 +204,15 @@ public void AddProvider_ConfiguresPolicyNameAcrossMultipleProviderSetups(int pro .AddProvider("test2", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 4 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) .AddProvider("test", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 5 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) .AddProvider("test", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 6 => _systemUnderTest - .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) .AddProvider("test2", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 7 => _systemUnderTest @@ -220,9 +221,9 @@ public void AddProvider_ConfiguresPolicyNameAcrossMultipleProviderSetups(int pro .AddProvider("test2", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 8 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) - .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }) - .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), _ => throw new InvalidOperationException("Invalid mode.") }; From 7e26ba176788086e10d3211b16434ac3d631fb52 Mon Sep 17 00:00:00 2001 From: Artyom Tonoyan Date: Sun, 5 Oct 2025 01:14:40 +0400 Subject: [PATCH 3/9] Refactor AddProvider API for simplicity and consistency Simplified the `AddProvider` API by removing the `TFeatureProvider` generic type parameter and directly using the `FeatureProvider` type. Updated the `TOptions` parameter to ensure it derives from `OpenFeatureProviderOptions`. Added a `` to `OpenFeature.csproj` in `OpenFeature.DependencyInjection.Abstractions.csproj`. Updated `OpenFeatureOptions` to `OpenFeatureProviderOptions` in the default name selector policy. Refactored `AddInMemoryProvider` methods to align with the new API. Updated tests in `OpenFeatureBuilderExtensionsTests` to reflect the changes and validate the updated functionality. Performed general code cleanup, including XML documentation updates, to improve clarity and maintain consistency across the codebase. --- ...re.DependencyInjection.Abstractions.csproj | 4 +++ .../OpenFeatureProviderBuilderExtensions.cs | 28 ++++++---------- .../OpenFeatureServiceCollectionExtensions.cs | 3 +- .../Memory/FeatureBuilderExtensions.cs | 4 +-- .../OpenFeatureBuilderExtensionsTests.cs | 32 +++++++++---------- 5 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj index e39768703..be60d49d9 100644 --- a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj +++ b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeature.DependencyInjection.Abstractions.csproj @@ -15,4 +15,8 @@ + + + + diff --git a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilderExtensions.cs b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilderExtensions.cs index 4fd76aa3d..02e4dc86b 100644 --- a/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilderExtensions.cs +++ b/src/OpenFeature.DependencyInjection.Abstractions/OpenFeatureProviderBuilderExtensions.cs @@ -16,34 +16,30 @@ public static partial class OpenFeatureProviderBuilderExtensions /// Adds a feature provider using a factory method without additional configuration options. /// This method adds the feature provider as a transient service and sets it as the default provider within the application. /// - /// The concrete feature provider type to register as the default. /// The used to configure feature flags. /// - /// A factory method that creates and returns a + /// A factory method that creates and returns a /// instance based on the provided service provider. /// /// The updated instance with the default feature provider set and configured. /// Thrown if the is null, as a valid builder is required to add and configure providers. - public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, Func implementationFactory) - where TFeatureProvider : class - => AddProvider(builder, implementationFactory, null); + public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, Func implementationFactory) + => AddProvider(builder, implementationFactory, null); /// /// Adds a feature provider using a factory method to create the provider instance and optionally configures its settings. /// This method adds the feature provider as a transient service and sets it as the default provider within the application. /// - /// The concrete feature provider type to register as the default. - /// Type derived from used to configure the feature provider. + /// Type derived from used to configure the feature provider. /// The used to configure feature flags. /// - /// A factory method that creates and returns a + /// A factory method that creates and returns a /// instance based on the provided service provider. /// /// An optional delegate to configure the provider-specific options. /// The updated instance with the default feature provider set and configured. /// Thrown if the is null, as a valid builder is required to add and configure providers. - public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, Func implementationFactory, Action? configureOptions) - where TFeatureProvider : class + public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, Func implementationFactory, Action? configureOptions) where TOptions : OpenFeatureProviderOptions { if (builder == null) throw new ArgumentNullException(nameof(builder)); @@ -63,8 +59,7 @@ public static OpenFeatureProviderBuilder AddProvider /// /// Adds a feature provider for a specific domain using provided options and a configuration builder. /// - /// The concrete feature provider type to register as the default. - /// Type derived from used to configure the feature provider. + /// Type derived from used to configure the feature provider. /// The used to configure feature flags. /// The unique name of the provider. /// @@ -76,8 +71,7 @@ public static OpenFeatureProviderBuilder AddProvider /// /// Thrown if either or is null or if the is empty. /// - public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, string domain, Func implementationFactory, Action? configureOptions) - where TFeatureProvider : class + public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, string domain, Func implementationFactory, Action? configureOptions) where TOptions : OpenFeatureProviderOptions { if (builder == null) throw new ArgumentNullException(nameof(builder)); @@ -107,7 +101,6 @@ public static OpenFeatureProviderBuilder AddProvider /// Adds a feature provider for a specified domain using the default options. /// This method configures a feature provider without custom options, delegating to the more generic AddProvider method. /// - /// The concrete feature provider type to register as the default. /// The used to configure feature flags. /// The unique name of the provider. /// @@ -118,9 +111,8 @@ public static OpenFeatureProviderBuilder AddProvider /// /// Thrown if either or is null or if the is empty. /// - public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, string domain, Func implementationFactory) - where TFeatureProvider : class - => AddProvider(builder, domain, implementationFactory, configureOptions: null); + public static OpenFeatureProviderBuilder AddProvider(this OpenFeatureProviderBuilder builder, string domain, Func implementationFactory) + => AddProvider(builder, domain, implementationFactory, configureOptions: null); /// /// Configures policy name options for OpenFeature using the specified options type. diff --git a/src/OpenFeature.Hosting/OpenFeatureServiceCollectionExtensions.cs b/src/OpenFeature.Hosting/OpenFeatureServiceCollectionExtensions.cs index 236dc62b0..ba2f1b0d0 100644 --- a/src/OpenFeature.Hosting/OpenFeatureServiceCollectionExtensions.cs +++ b/src/OpenFeature.Hosting/OpenFeatureServiceCollectionExtensions.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; +using OpenFeature.DependencyInjection.Abstractions; using OpenFeature.Hosting; using OpenFeature.Hosting.Internal; @@ -49,7 +50,7 @@ public static IServiceCollection AddOpenFeature(this IServiceCollection services { options.DefaultNameSelector = provider => { - var options = provider.GetRequiredService>().Value; + var options = provider.GetRequiredService>().Value; return options.ProviderNames.First(); }; }); diff --git a/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs b/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs index f7f1fb8ac..fc9b442d9 100644 --- a/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs +++ b/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs @@ -79,7 +79,7 @@ public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeaturePro /// /// The instance for chaining. public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, Action>? configure = null) - => builder.AddProvider(CreateProvider, options => ConfigureFlags(options, configure)); + => builder.AddProvider(CreateProvider, options => ConfigureFlags(options, configure)); /// /// Adds an in-memory feature provider with a specific domain to the with optional flag configuration. @@ -92,7 +92,7 @@ public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeaturePro /// /// The instance for chaining. public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, string domain, Action>? configure = null) - => builder.AddProvider(domain, CreateProvider, options => ConfigureFlags(options, configure)); + => builder.AddProvider(domain, CreateProvider, options => ConfigureFlags(options, configure)); private static InMemoryProvider CreateProvider(IServiceProvider provider, string domain) { diff --git a/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs b/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs index 414ae384a..1854f5f8d 100644 --- a/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs +++ b/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs @@ -73,8 +73,8 @@ public void AddProvider_ShouldAddProviderToCollection(int providerRegistrationTy { 1 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider()), 2 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider()), - 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), - 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), + 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), + 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), _ => throw new InvalidOperationException("Invalid mode.") }; @@ -106,8 +106,8 @@ public void AddProvider_ShouldResolveCorrectProvider(int providerRegistrationTyp { 1 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider()), 2 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider()), - 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), - 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), + 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), + 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), _ => throw new InvalidOperationException("Invalid mode.") }; @@ -150,22 +150,22 @@ public void AddProvider_VerifiesDefaultAndDomainBoundProvidersBasedOnConfigurati .AddProvider("test1", (_, _) => new NoOpFeatureProvider()) .AddProvider("test2", (_, _) => new NoOpFeatureProvider()), 4 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) .AddProvider("test", (_, _) => new NoOpFeatureProvider()), 5 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) .AddProvider("test", (_, _) => new NoOpFeatureProvider()), 6 => _systemUnderTest - .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) .AddProvider("test2", (_, _) => new NoOpFeatureProvider()), 7 => _systemUnderTest .AddProvider(_ => new NoOpFeatureProvider()) .AddProvider("test", (_, _) => new NoOpFeatureProvider()) .AddProvider("test2", (_, _) => new NoOpFeatureProvider()), 8 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) - .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }) - .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }), + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }), _ => throw new InvalidOperationException("Invalid mode.") }; @@ -204,15 +204,15 @@ public void AddProvider_ConfiguresPolicyNameAcrossMultipleProviderSetups(int pro .AddProvider("test2", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 4 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) .AddProvider("test", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 5 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) .AddProvider("test", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 6 => _systemUnderTest - .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) .AddProvider("test2", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 7 => _systemUnderTest @@ -221,9 +221,9 @@ public void AddProvider_ConfiguresPolicyNameAcrossMultipleProviderSetups(int pro .AddProvider("test2", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 8 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) - .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }) - .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), _ => throw new InvalidOperationException("Invalid mode.") }; From 1edb0c963c4860cd54a42dbd7e6120b0043436e1 Mon Sep 17 00:00:00 2001 From: Artyom Tonoyan Date: Sun, 5 Oct 2025 01:53:42 +0400 Subject: [PATCH 4/9] Refactor FeatureLifecycleManager initialization Refactored `FeatureLifecycleManager` to modularize provider, hook, and handler initialization with new methods: `InitializeProvidersAsync`, `InitializeHooks`, and `InitializeHandlers`. Updated `EnsureInitializedAsync` to use these methods for improved readability and maintainability. Revised `AddInMemoryProvider` in `FeatureBuilderExtensions` to use a generic `FeatureProvider` abstraction. Adjusted `CreateProvider` methods accordingly. Improved code clarity in `FeatureFlagIntegrationTest` by renaming variables for consistency and removing redundant assignments. --- .../Internal/FeatureLifecycleManager.cs | 19 ++++++++++++++++++- .../Memory/FeatureBuilderExtensions.cs | 4 ++-- .../FeatureFlagIntegrationTest.cs | 5 ++--- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs b/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs index 4d915946b..433831d4d 100644 --- a/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs +++ b/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using OpenFeature.DependencyInjection.Abstractions; namespace OpenFeature.Hosting.Internal; @@ -22,7 +23,15 @@ public async ValueTask EnsureInitializedAsync(CancellationToken cancellationToke { this.LogStartingInitializationOfFeatureProvider(); - var options = _serviceProvider.GetRequiredService>().Value; + await InitializeProvidersAsync(cancellationToken).ConfigureAwait(false); + InitializeHooks(); + InitializeHandlers(); + } + + /// + private async Task InitializeProvidersAsync(CancellationToken cancellationToken) + { + var options = _serviceProvider.GetRequiredService>().Value; if (options.HasDefaultProvider) { var featureProvider = _serviceProvider.GetRequiredService(); @@ -34,7 +43,12 @@ public async ValueTask EnsureInitializedAsync(CancellationToken cancellationToke var featureProvider = _serviceProvider.GetRequiredKeyedService(name); await _featureApi.SetProviderAsync(name, featureProvider).ConfigureAwait(false); } + } + /// + private void InitializeHooks() + { + var options = _serviceProvider.GetRequiredService>().Value; var hooks = new List(); foreach (var hookName in options.HookNames) { @@ -43,7 +57,10 @@ public async ValueTask EnsureInitializedAsync(CancellationToken cancellationToke } _featureApi.AddHooks(hooks); + } + private void InitializeHandlers() + { var handlers = _serviceProvider.GetServices(); foreach (var handler in handlers) { diff --git a/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs b/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs index fc9b442d9..68d55bb83 100644 --- a/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs +++ b/src/OpenFeature.Hosting/Providers/Memory/FeatureBuilderExtensions.cs @@ -94,7 +94,7 @@ public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeaturePro public static OpenFeatureProviderBuilder AddInMemoryProvider(this OpenFeatureProviderBuilder builder, string domain, Action>? configure = null) => builder.AddProvider(domain, CreateProvider, options => ConfigureFlags(options, configure)); - private static InMemoryProvider CreateProvider(IServiceProvider provider, string domain) + private static FeatureProvider CreateProvider(IServiceProvider provider, string domain) { var options = provider.GetRequiredService>().Get(domain); if (options.Flags == null) @@ -105,7 +105,7 @@ private static InMemoryProvider CreateProvider(IServiceProvider provider, string return new InMemoryProvider(options.Flags); } - private static InMemoryProvider CreateProvider(IServiceProvider provider) + private static FeatureProvider CreateProvider(IServiceProvider provider) { var options = provider.GetRequiredService>().Value; if (options.Flags == null) diff --git a/test/OpenFeature.IntegrationTests/FeatureFlagIntegrationTest.cs b/test/OpenFeature.IntegrationTests/FeatureFlagIntegrationTest.cs index 9638ff8c1..e68d56d7a 100644 --- a/test/OpenFeature.IntegrationTests/FeatureFlagIntegrationTest.cs +++ b/test/OpenFeature.IntegrationTests/FeatureFlagIntegrationTest.cs @@ -224,8 +224,8 @@ private static async Task CreateServerAsync(ServiceLifetime serviceL { if (serviceLifetime == ServiceLifetime.Scoped) { - using var scoped = provider.CreateScope(); - var flagService = scoped.ServiceProvider.GetRequiredService(); + using var scope = provider.CreateScope(); + var flagService = scope.ServiceProvider.GetRequiredService(); return flagService.GetFlags(); } else @@ -248,7 +248,6 @@ private static async Task CreateServerAsync(ServiceLifetime serviceL { var client = context.RequestServices.GetRequiredService(); var featureName = UserInfoHelper.GetFeatureName(context); - var res = await client.GetBooleanValueAsync(featureName, false).ConfigureAwait(true); var result = await client.GetBooleanValueAsync(featureName, false).ConfigureAwait(true); var response = new FeatureFlagResponse(featureName, result); From 6cfaeb7598ec5110de20438688be74951f57eb89 Mon Sep 17 00:00:00 2001 From: Artyom Tonoyan Date: Sun, 5 Oct 2025 02:27:07 +0400 Subject: [PATCH 5/9] Refactor tests to use OpenFeatureProviderOptions Updated FeatureLifecycleManagerTests to replace OpenFeatureOptions with OpenFeatureProviderOptions for configuring feature providers. Added support for hooks and keyed singletons to enhance modularity. Introduced additional feature flag retrieval in FeatureFlagIntegrationTest. Added dependency on OpenFeature.DependencyInjection.Abstractions. --- .../Internal/FeatureLifecycleManagerTests.cs | 14 +++++++++----- .../FeatureFlagIntegrationTest.cs | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/test/OpenFeature.Hosting.Tests/Internal/FeatureLifecycleManagerTests.cs b/test/OpenFeature.Hosting.Tests/Internal/FeatureLifecycleManagerTests.cs index 2d379fc4e..cac259dac 100644 --- a/test/OpenFeature.Hosting.Tests/Internal/FeatureLifecycleManagerTests.cs +++ b/test/OpenFeature.Hosting.Tests/Internal/FeatureLifecycleManagerTests.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; using OpenFeature.Constant; +using OpenFeature.DependencyInjection.Abstractions; using OpenFeature.Hosting.Internal; namespace OpenFeature.Hosting.Tests.Internal; @@ -15,7 +16,7 @@ public async Task EnsureInitializedAsync_SetsProvider() // Arrange var services = new ServiceCollection(); var provider = new NoOpFeatureProvider(); - services.AddOptions().Configure(options => + services.AddOptions().Configure(options => { options.AddProviderName(null); }); @@ -40,7 +41,7 @@ public async Task EnsureInitializedAsync_SetsMultipleProvider() var services = new ServiceCollection(); var provider1 = new NoOpFeatureProvider(); var provider2 = new NoOpFeatureProvider(); - services.AddOptions().Configure(options => + services.AddOptions().Configure(options => { options.AddProviderName("provider1"); options.AddProviderName("provider2"); @@ -67,9 +68,12 @@ public async Task EnsureInitializedAsync_AddsHooks() var services = new ServiceCollection(); var provider = new NoOpFeatureProvider(); var hook = new NoOpHook(); - services.AddOptions().Configure(options => + services.AddOptions().Configure(options => { options.AddProviderName(null); + }); + services.AddOptions().Configure(options => + { options.AddHookName("TestHook"); }); services.AddSingleton(provider); @@ -94,7 +98,7 @@ public async Task EnsureInitializedAsync_AddHandlers() // Arrange var services = new ServiceCollection(); var provider = new NoOpFeatureProvider(); - services.AddOptions().Configure(options => + services.AddOptions().Configure(options => { options.AddProviderName(null); }); @@ -169,7 +173,7 @@ public async Task ShutdownAsync_LogShuttingDown() // Arrange var services = new ServiceCollection(); var provider = new NoOpFeatureProvider(); - services.AddOptions().Configure(options => + services.AddOptions().Configure(options => { options.AddProviderName(null); }); diff --git a/test/OpenFeature.IntegrationTests/FeatureFlagIntegrationTest.cs b/test/OpenFeature.IntegrationTests/FeatureFlagIntegrationTest.cs index e68d56d7a..82a286cca 100644 --- a/test/OpenFeature.IntegrationTests/FeatureFlagIntegrationTest.cs +++ b/test/OpenFeature.IntegrationTests/FeatureFlagIntegrationTest.cs @@ -248,6 +248,7 @@ private static async Task CreateServerAsync(ServiceLifetime serviceL { var client = context.RequestServices.GetRequiredService(); var featureName = UserInfoHelper.GetFeatureName(context); + var res = await client.GetBooleanValueAsync(featureName, false).ConfigureAwait(true); var result = await client.GetBooleanValueAsync(featureName, false).ConfigureAwait(true); var response = new FeatureFlagResponse(featureName, result); From 3d680f3f054cba3b9547f276d09a8016bf26c66d Mon Sep 17 00:00:00 2001 From: Artyom Tonoyan Date: Mon, 27 Oct 2025 09:08:54 +0400 Subject: [PATCH 6/9] Removev ID property --- OpenFeature.slnx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenFeature.slnx b/OpenFeature.slnx index 7a386eaf4..060c0b388 100644 --- a/OpenFeature.slnx +++ b/OpenFeature.slnx @@ -51,7 +51,7 @@ - + From 4b1237a77630db55164c241dc91f96835a44d1a6 Mon Sep 17 00:00:00 2001 From: Artyom Tonoyan Date: Wed, 5 Nov 2025 23:47:00 +0400 Subject: [PATCH 7/9] Update src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Silva <2493377+askpt@users.noreply.github.com> Signed-off-by: Artyom Tonoyan --- src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs b/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs index 433831d4d..3a98a00a7 100644 --- a/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs +++ b/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs @@ -28,7 +28,6 @@ public async ValueTask EnsureInitializedAsync(CancellationToken cancellationToke InitializeHandlers(); } - /// private async Task InitializeProvidersAsync(CancellationToken cancellationToken) { var options = _serviceProvider.GetRequiredService>().Value; From 04ea06398013d991c498e26caac6fd78ee06adb0 Mon Sep 17 00:00:00 2001 From: Artyom Tonoyan Date: Wed, 5 Nov 2025 23:47:12 +0400 Subject: [PATCH 8/9] Update src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: André Silva <2493377+askpt@users.noreply.github.com> Signed-off-by: Artyom Tonoyan --- src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs b/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs index 3a98a00a7..332bc5ad4 100644 --- a/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs +++ b/src/OpenFeature.Hosting/Internal/FeatureLifecycleManager.cs @@ -44,7 +44,6 @@ private async Task InitializeProvidersAsync(CancellationToken cancellationToken) } } - /// private void InitializeHooks() { var options = _serviceProvider.GetRequiredService>().Value; From f52132178ebf244fd8de653cbe5897f42c731c33 Mon Sep 17 00:00:00 2001 From: Artyom Tonoyan Date: Thu, 6 Nov 2025 01:02:49 +0400 Subject: [PATCH 9/9] Refactor OpenFeatureOptions to OpenFeatureProviderOptions Refactored `OpenFeatureOptions` to `OpenFeatureProviderOptions` to improve clarity and maintainability. Updated all references and test cases to use the new class. Removed inheritance of `OpenFeatureOptions` from `OpenFeatureProviderOptions` and introduced `_hookNames` for managing hook names. Replaced `TestOptions` with `TestProviderOptions` in `OpenFeatureBuilderExtensionsTests`, adding a `SomeFlag` property for more flexible testing. Renamed and updated `OpenFeatureOptionsTests` to `OpenFeatureProviderOptionsTests`. Performed general cleanup, including removing unused namespaces, ensuring consistent naming conventions, and aligning method calls with the new class structure. --- src/OpenFeature.Hosting/OpenFeatureOptions.cs | 4 +- .../Internal/FeatureLifecycleManagerTests.cs | 2 +- .../OpenFeatureBuilderExtensionsTests.cs | 37 ++++++++++--------- ....cs => OpenFeatureProviderOptionsTests.cs} | 12 +++--- 4 files changed, 29 insertions(+), 26 deletions(-) rename test/OpenFeature.Hosting.Tests/{OpenFeatureOptionsTests.cs => OpenFeatureProviderOptionsTests.cs} (81%) diff --git a/src/OpenFeature.Hosting/OpenFeatureOptions.cs b/src/OpenFeature.Hosting/OpenFeatureOptions.cs index de2cdc2c8..be4788378 100644 --- a/src/OpenFeature.Hosting/OpenFeatureOptions.cs +++ b/src/OpenFeature.Hosting/OpenFeatureOptions.cs @@ -1,11 +1,9 @@ -using OpenFeature.DependencyInjection.Abstractions; - namespace OpenFeature.Hosting; /// /// Options to configure OpenFeature /// -public class OpenFeatureOptions : OpenFeatureProviderOptions +public class OpenFeatureOptions { private readonly HashSet _hookNames = []; diff --git a/test/OpenFeature.Hosting.Tests/Internal/FeatureLifecycleManagerTests.cs b/test/OpenFeature.Hosting.Tests/Internal/FeatureLifecycleManagerTests.cs index cac259dac..9437ae1b9 100644 --- a/test/OpenFeature.Hosting.Tests/Internal/FeatureLifecycleManagerTests.cs +++ b/test/OpenFeature.Hosting.Tests/Internal/FeatureLifecycleManagerTests.cs @@ -146,7 +146,7 @@ public async Task EnsureInitializedAsync_LogStartingInitialization() // Arrange var services = new ServiceCollection(); var provider = new NoOpFeatureProvider(); - services.AddOptions().Configure(options => + services.AddOptions().Configure(options => { options.AddProviderName(null); }); diff --git a/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs b/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs index 1854f5f8d..0485eb313 100644 --- a/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs +++ b/test/OpenFeature.Hosting.Tests/OpenFeatureBuilderExtensionsTests.cs @@ -73,8 +73,8 @@ public void AddProvider_ShouldAddProviderToCollection(int providerRegistrationTy { 1 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider()), 2 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider()), - 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), - 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), + 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { o.SomeFlag = true; }), + 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { o.SomeFlag = true; }), _ => throw new InvalidOperationException("Invalid mode.") }; @@ -89,7 +89,10 @@ public void AddProvider_ShouldAddProviderToCollection(int providerRegistrationTy serviceDescriptor.Lifetime == ServiceLifetime.Transient); } - class TestOptions : OpenFeatureOptions { } + internal sealed class TestProviderOptions : OpenFeatureProviderOptions + { + public bool SomeFlag { get; set; } + } #if NET8_0_OR_GREATER [System.Diagnostics.CodeAnalysis.Experimental(DependencyInjection.Abstractions.Diagnostics.FeatureCodes.NewDi)] @@ -106,8 +109,8 @@ public void AddProvider_ShouldResolveCorrectProvider(int providerRegistrationTyp { 1 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider()), 2 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider()), - 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), - 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), + 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), + 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), _ => throw new InvalidOperationException("Invalid mode.") }; @@ -150,22 +153,22 @@ public void AddProvider_VerifiesDefaultAndDomainBoundProvidersBasedOnConfigurati .AddProvider("test1", (_, _) => new NoOpFeatureProvider()) .AddProvider("test2", (_, _) => new NoOpFeatureProvider()), 4 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) .AddProvider("test", (_, _) => new NoOpFeatureProvider()), 5 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) .AddProvider("test", (_, _) => new NoOpFeatureProvider()), 6 => _systemUnderTest - .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) .AddProvider("test2", (_, _) => new NoOpFeatureProvider()), 7 => _systemUnderTest .AddProvider(_ => new NoOpFeatureProvider()) .AddProvider("test", (_, _) => new NoOpFeatureProvider()) .AddProvider("test2", (_, _) => new NoOpFeatureProvider()), 8 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) - .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }) - .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }), + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }), _ => throw new InvalidOperationException("Invalid mode.") }; @@ -204,15 +207,15 @@ public void AddProvider_ConfiguresPolicyNameAcrossMultipleProviderSetups(int pro .AddProvider("test2", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 4 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) .AddProvider("test", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 5 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) .AddProvider("test", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 6 => _systemUnderTest - .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) .AddProvider("test2", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 7 => _systemUnderTest @@ -221,9 +224,9 @@ public void AddProvider_ConfiguresPolicyNameAcrossMultipleProviderSetups(int pro .AddProvider("test2", (_, _) => new NoOpFeatureProvider()) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), 8 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) - .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }) - .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider(_ => new NoOpFeatureProvider(), o => { }) + .AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }) + .AddProvider("test2", (_, _) => new NoOpFeatureProvider(), o => { }) .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), _ => throw new InvalidOperationException("Invalid mode.") }; diff --git a/test/OpenFeature.Hosting.Tests/OpenFeatureOptionsTests.cs b/test/OpenFeature.Hosting.Tests/OpenFeatureProviderOptionsTests.cs similarity index 81% rename from test/OpenFeature.Hosting.Tests/OpenFeatureOptionsTests.cs rename to test/OpenFeature.Hosting.Tests/OpenFeatureProviderOptionsTests.cs index d39d4059f..7a254ec1a 100644 --- a/test/OpenFeature.Hosting.Tests/OpenFeatureOptionsTests.cs +++ b/test/OpenFeature.Hosting.Tests/OpenFeatureProviderOptionsTests.cs @@ -1,12 +1,14 @@ +using OpenFeature.DependencyInjection.Abstractions; + namespace OpenFeature.Hosting.Tests; -public class OpenFeatureOptionsTests +public class OpenFeatureProviderOptionsTests { [Fact] public void AddProviderName_DoesNotSetHasDefaultProvider() { // Arrange - var options = new OpenFeatureOptions(); + var options = new OpenFeatureProviderOptions(); // Act options.AddProviderName("TestProvider"); @@ -19,7 +21,7 @@ public void AddProviderName_DoesNotSetHasDefaultProvider() public void AddProviderName_WithNullName_SetsHasDefaultProvider() { // Arrange - var options = new OpenFeatureOptions(); + var options = new OpenFeatureProviderOptions(); // Act options.AddProviderName(null); @@ -34,7 +36,7 @@ public void AddProviderName_WithNullName_SetsHasDefaultProvider() public void AddProviderName_WithEmptyName_SetsHasDefaultProvider(string name) { // Arrange - var options = new OpenFeatureOptions(); + var options = new OpenFeatureProviderOptions(); // Act options.AddProviderName(name); @@ -47,7 +49,7 @@ public void AddProviderName_WithEmptyName_SetsHasDefaultProvider(string name) public void AddProviderName_WithSameName_OnlyRegistersNameOnce() { // Arrange - var options = new OpenFeatureOptions(); + var options = new OpenFeatureProviderOptions(); // Act options.AddProviderName("test-provider");