diff --git a/OpenFeature.slnx b/OpenFeature.slnx index 936079f40..db8f40024 100644 --- a/OpenFeature.slnx +++ b/OpenFeature.slnx @@ -48,7 +48,6 @@ - @@ -58,7 +57,6 @@ - diff --git a/src/OpenFeature.DependencyInjection/Diagnostics/FeatureCodes.cs b/src/OpenFeature.DependencyInjection/Diagnostics/FeatureCodes.cs deleted file mode 100644 index 582ab39c9..000000000 --- a/src/OpenFeature.DependencyInjection/Diagnostics/FeatureCodes.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace OpenFeature.DependencyInjection.Diagnostics; - -/// -/// Contains identifiers for experimental features and diagnostics in the OpenFeature framework. -/// -/// -/// Experimental - This class includes identifiers that allow developers to track and conditionally enable -/// experimental features. Each identifier follows a structured code format to indicate the feature domain, -/// maturity level, and unique identifier. Note that experimental features are subject to change or removal -/// in future releases. -/// -/// Basic Information
-/// These identifiers conform to OpenFeature’s Diagnostics Specifications, allowing developers to recognize -/// and manage experimental features effectively. -///
-///
-/// -/// -/// Code Structure: -/// - "OF" - Represents the OpenFeature library. -/// - "DI" - Indicates the Dependency Injection domain. -/// - "001" - Unique identifier for a specific feature. -/// -/// -internal static class FeatureCodes -{ - /// - /// Identifier for the experimental Dependency Injection features within the OpenFeature framework. - /// - /// - /// OFDI001 identifier marks experimental features in the Dependency Injection (DI) domain. - /// - /// Usage: - /// Developers can use this identifier to conditionally enable or test experimental DI features. - /// It is part of the OpenFeature diagnostics system to help track experimental functionality. - /// - public const string NewDi = "OFDI001"; -} diff --git a/src/OpenFeature.DependencyInjection/Guard.cs b/src/OpenFeature.DependencyInjection/Guard.cs deleted file mode 100644 index 337a8290f..000000000 --- a/src/OpenFeature.DependencyInjection/Guard.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Diagnostics; -using System.Runtime.CompilerServices; - -namespace OpenFeature.DependencyInjection; - -[DebuggerStepThrough] -internal static class Guard -{ - public static void ThrowIfNull(object? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) - { - if (argument is null) - throw new ArgumentNullException(paramName); - } - - public static void ThrowIfNullOrWhiteSpace(string? argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) - { - if (string.IsNullOrWhiteSpace(argument)) - throw new ArgumentNullException(paramName); - } -} diff --git a/src/OpenFeature.DependencyInjection/IFeatureLifecycleManager.cs b/src/OpenFeature.DependencyInjection/IFeatureLifecycleManager.cs deleted file mode 100644 index 4891f2e8b..000000000 --- a/src/OpenFeature.DependencyInjection/IFeatureLifecycleManager.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace OpenFeature.DependencyInjection; - -/// -/// Defines the contract for managing the lifecycle of a feature api. -/// -public interface IFeatureLifecycleManager -{ - /// - /// Ensures that the feature provider is properly initialized and ready to be used. - /// This method should handle all necessary checks, configuration, and setup required to prepare the feature provider. - /// - /// Propagates notification that operations should be canceled. - /// A Task representing the asynchronous operation of initializing the feature provider. - /// Thrown when the feature provider is not registered or is in an invalid state. - ValueTask EnsureInitializedAsync(CancellationToken cancellationToken = default); - - /// - /// Gracefully shuts down the feature api, ensuring all resources are properly disposed of and any persistent state is saved. - /// This method should handle all necessary cleanup and shutdown operations for the feature provider. - /// - /// Propagates notification that operations should be canceled. - /// A Task representing the asynchronous operation of shutting down the feature provider. - ValueTask ShutdownAsync(CancellationToken cancellationToken = default); -} diff --git a/src/OpenFeature.DependencyInjection/Internal/EventHandlerDelegateWrapper.cs b/src/OpenFeature.DependencyInjection/Internal/EventHandlerDelegateWrapper.cs deleted file mode 100644 index d31b3355c..000000000 --- a/src/OpenFeature.DependencyInjection/Internal/EventHandlerDelegateWrapper.cs +++ /dev/null @@ -1,8 +0,0 @@ -using OpenFeature.Constant; -using OpenFeature.Model; - -namespace OpenFeature.DependencyInjection.Internal; - -internal record EventHandlerDelegateWrapper( - ProviderEventTypes ProviderEventType, - EventHandlerDelegate EventHandlerDelegate); diff --git a/src/OpenFeature.DependencyInjection/Internal/FeatureLifecycleManager.cs b/src/OpenFeature.DependencyInjection/Internal/FeatureLifecycleManager.cs deleted file mode 100644 index 1ecac4349..000000000 --- a/src/OpenFeature.DependencyInjection/Internal/FeatureLifecycleManager.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace OpenFeature.DependencyInjection.Internal; - -internal sealed partial class FeatureLifecycleManager : IFeatureLifecycleManager -{ - private readonly Api _featureApi; - private readonly IServiceProvider _serviceProvider; - private readonly ILogger _logger; - - public FeatureLifecycleManager(Api featureApi, IServiceProvider serviceProvider, ILogger logger) - { - _featureApi = featureApi; - _serviceProvider = serviceProvider; - _logger = logger; - } - - /// - public async ValueTask EnsureInitializedAsync(CancellationToken cancellationToken = default) - { - this.LogStartingInitializationOfFeatureProvider(); - - var options = _serviceProvider.GetRequiredService>().Value; - if (options.HasDefaultProvider) - { - var featureProvider = _serviceProvider.GetRequiredService(); - await _featureApi.SetProviderAsync(featureProvider).ConfigureAwait(false); - } - - foreach (var name in options.ProviderNames) - { - var featureProvider = _serviceProvider.GetRequiredKeyedService(name); - await _featureApi.SetProviderAsync(name, featureProvider).ConfigureAwait(false); - } - - var hooks = new List(); - foreach (var hookName in options.HookNames) - { - var hook = _serviceProvider.GetRequiredKeyedService(hookName); - hooks.Add(hook); - } - - _featureApi.AddHooks(hooks); - - var handlers = _serviceProvider.GetServices(); - foreach (var handler in handlers) - { - _featureApi.AddHandler(handler.ProviderEventType, handler.EventHandlerDelegate); - } - } - - /// - public async ValueTask ShutdownAsync(CancellationToken cancellationToken = default) - { - this.LogShuttingDownFeatureProvider(); - await _featureApi.ShutdownAsync().ConfigureAwait(false); - } - - [LoggerMessage(200, LogLevel.Information, "Starting initialization of the feature provider")] - partial void LogStartingInitializationOfFeatureProvider(); - - [LoggerMessage(200, LogLevel.Information, "Shutting down the feature provider")] - partial void LogShuttingDownFeatureProvider(); -} diff --git a/src/OpenFeature.DependencyInjection/MultiTarget/CallerArgumentExpressionAttribute.cs b/src/OpenFeature.DependencyInjection/MultiTarget/CallerArgumentExpressionAttribute.cs deleted file mode 100644 index afbec6b06..000000000 --- a/src/OpenFeature.DependencyInjection/MultiTarget/CallerArgumentExpressionAttribute.cs +++ /dev/null @@ -1,23 +0,0 @@ -// @formatter:off -// ReSharper disable All -#if NETCOREAPP3_0_OR_GREATER -// https://github.com/dotnet/runtime/issues/96197 -[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.CallerArgumentExpressionAttribute))] -#else -#pragma warning disable -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Runtime.CompilerServices; - -[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] -internal sealed class CallerArgumentExpressionAttribute : Attribute -{ - public CallerArgumentExpressionAttribute(string parameterName) - { - ParameterName = parameterName; - } - - public string ParameterName { get; } -} -#endif diff --git a/src/OpenFeature.DependencyInjection/MultiTarget/IsExternalInit.cs b/src/OpenFeature.DependencyInjection/MultiTarget/IsExternalInit.cs deleted file mode 100644 index 877141115..000000000 --- a/src/OpenFeature.DependencyInjection/MultiTarget/IsExternalInit.cs +++ /dev/null @@ -1,21 +0,0 @@ -// @formatter:off -// ReSharper disable All -#if NET5_0_OR_GREATER -// https://github.com/dotnet/runtime/issues/96197 -[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.IsExternalInit))] -#else -#pragma warning disable -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.ComponentModel; - -namespace System.Runtime.CompilerServices; - -/// -/// Reserved to be used by the compiler for tracking metadata. -/// This class should not be used by developers in source code. -/// -[EditorBrowsable(EditorBrowsableState.Never)] -static class IsExternalInit { } -#endif diff --git a/src/OpenFeature.DependencyInjection/OpenFeature.DependencyInjection.csproj b/src/OpenFeature.DependencyInjection/OpenFeature.DependencyInjection.csproj deleted file mode 100644 index afefeb9a9..000000000 --- a/src/OpenFeature.DependencyInjection/OpenFeature.DependencyInjection.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - OpenFeature.DependencyInjection - README.md - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/OpenFeature.DependencyInjection/OpenFeatureBuilder.cs b/src/OpenFeature.DependencyInjection/OpenFeatureBuilder.cs deleted file mode 100644 index ae1e8c8fb..000000000 --- a/src/OpenFeature.DependencyInjection/OpenFeatureBuilder.cs +++ /dev/null @@ -1,60 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace OpenFeature.DependencyInjection; - -/// -/// Describes a backed by an . -/// -/// The services being configured. -public class OpenFeatureBuilder(IServiceCollection 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 - /// should be initialized based on the presence of an evaluation context. - /// - 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. - /// - 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; } - - /// - /// 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) - { - 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."); - } - } - } -} diff --git a/src/OpenFeature.DependencyInjection/OpenFeatureBuilderExtensions.cs b/src/OpenFeature.DependencyInjection/OpenFeatureBuilderExtensions.cs deleted file mode 100644 index d676dc5e9..000000000 --- a/src/OpenFeature.DependencyInjection/OpenFeatureBuilderExtensions.cs +++ /dev/null @@ -1,382 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; -using OpenFeature.Constant; -using OpenFeature.DependencyInjection; -using OpenFeature.DependencyInjection.Internal; -using OpenFeature.Model; - -namespace OpenFeature; - -/// -/// Contains extension methods for the class. -/// -#if NET8_0_OR_GREATER -[System.Diagnostics.CodeAnalysis.Experimental(DependencyInjection.Diagnostics.FeatureCodes.NewDi)] -#endif -public static partial class OpenFeatureBuilderExtensions -{ - /// - /// This method is used to add a new context to the service collection. - /// - /// The instance. - /// the desired configuration - /// The instance. - /// Thrown when the or action is null. - public static OpenFeatureBuilder AddContext(this OpenFeatureBuilder builder, Action configure) - { - Guard.ThrowIfNull(builder); - Guard.ThrowIfNull(configure); - - return builder.AddContext((b, _) => configure(b)); - } - - /// - /// This method is used to add a new context to the service collection. - /// - /// The instance. - /// the desired configuration - /// The instance. - /// Thrown when the or action is null. - public static OpenFeatureBuilder AddContext(this OpenFeatureBuilder builder, Action configure) - { - Guard.ThrowIfNull(builder); - Guard.ThrowIfNull(configure); - - builder.IsContextConfigured = true; - builder.Services.TryAddTransient(provider => - { - var contextBuilder = EvaluationContext.Builder(); - configure(contextBuilder, provider); - return contextBuilder.Build(); - }); - - 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 - /// depending on the policy name selected. - /// If no name is selected (i.e., null), it retrieves the default client. - /// - /// The instance. - /// The configured instance. - internal static OpenFeatureBuilder AddPolicyBasedClient(this OpenFeatureBuilder builder) - { - builder.Services.AddScoped(provider => - { - var policy = provider.GetRequiredService>().Value; - var name = policy.DefaultNameSelector(provider); - if (name == null) - { - return provider.GetRequiredService(); - } - return provider.GetRequiredKeyedService(name); - }); - - 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. - /// - /// The type of to be added. - /// The instance. - /// Optional factory for controlling how will be created in the DI container. - /// The instance. - public static OpenFeatureBuilder AddHook< -#if NET - [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] -#endif - THook>(this OpenFeatureBuilder builder, Func? implementationFactory = null) - where THook : Hook - { - return builder.AddHook(typeof(THook).Name, implementationFactory); - } - - /// - /// Adds a feature hook to the service collection. Hooks added here are not domain-bound. - /// - /// The type of to be added. - /// The instance. - /// Instance of Hook to inject into the OpenFeature context. - /// The instance. - public static OpenFeatureBuilder AddHook< -#if NET - [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] -#endif - THook>(this OpenFeatureBuilder builder, THook hook) - where THook : Hook - { - return builder.AddHook(typeof(THook).Name, hook); - } - - /// - /// Adds a feature hook to the service collection with a specified name. Hooks added here are not domain-bound. - /// - /// The type of to be added. - /// The instance. - /// The name of the that is being added. - /// Instance of Hook to inject into the OpenFeature context. - /// The instance. - public static OpenFeatureBuilder AddHook< -#if NET - [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] -#endif - THook>(this OpenFeatureBuilder builder, string hookName, THook hook) - where THook : Hook - { - return builder.AddHook(hookName, _ => hook); - } - - /// - /// Adds a feature hook to the service collection using a factory method and specified name. Hooks added here are not domain-bound. - /// - /// The type of to be added. - /// The instance. - /// The name of the that is being added. - /// Optional factory for controlling how will be created in the DI container. - /// The instance. - public static OpenFeatureBuilder AddHook< -#if NET - [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] -#endif - THook> - (this OpenFeatureBuilder builder, string hookName, Func? implementationFactory = null) - where THook : Hook - { - builder.Services.PostConfigure(options => options.AddHookName(hookName)); - - if (implementationFactory is not null) - { - builder.Services.TryAddKeyedSingleton(hookName, (serviceProvider, key) => - { - return implementationFactory(serviceProvider); - }); - } - else - { - builder.Services.TryAddKeyedSingleton(hookName); - } - - return builder; - } - - /// - /// Add a to allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions - /// - /// The instance. - /// The type to handle. - /// The handler which reacts to . - /// The instance. - public static OpenFeatureBuilder AddHandler(this OpenFeatureBuilder builder, ProviderEventTypes type, EventHandlerDelegate eventHandlerDelegate) - { - return AddHandler(builder, type, _ => eventHandlerDelegate); - } - - /// - /// Add a to allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions - /// - /// The instance. - /// The type to handle. - /// The handler factory for creating a handler which reacts to . - /// The instance. - public static OpenFeatureBuilder AddHandler(this OpenFeatureBuilder builder, ProviderEventTypes type, Func implementationFactory) - { - builder.Services.AddSingleton((serviceProvider) => - { - var handler = implementationFactory(serviceProvider); - return new EventHandlerDelegateWrapper(type, handler); - }); - - return builder; - } -} diff --git a/src/OpenFeature.DependencyInjection/OpenFeatureOptions.cs b/src/OpenFeature.DependencyInjection/OpenFeatureOptions.cs deleted file mode 100644 index e9cc3cb12..000000000 --- a/src/OpenFeature.DependencyInjection/OpenFeatureOptions.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace OpenFeature.DependencyInjection; - -/// -/// Options to configure OpenFeature -/// -public class OpenFeatureOptions -{ - 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; - - internal void AddHookName(string name) - { - lock (_hookNames) - { - _hookNames.Add(name); - } - } -} diff --git a/src/OpenFeature.DependencyInjection/OpenFeatureServiceCollectionExtensions.cs b/src/OpenFeature.DependencyInjection/OpenFeatureServiceCollectionExtensions.cs deleted file mode 100644 index a24c67e78..000000000 --- a/src/OpenFeature.DependencyInjection/OpenFeatureServiceCollectionExtensions.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Options; -using OpenFeature.DependencyInjection; -using OpenFeature.DependencyInjection.Internal; - -namespace OpenFeature; - -/// -/// Contains extension methods for the class. -/// -public static partial class OpenFeatureServiceCollectionExtensions -{ - /// - /// Adds and configures OpenFeature services to the provided . - /// - /// The instance. - /// A configuration action for customizing OpenFeature setup via - /// The modified instance - /// Thrown if or is null. - public static IServiceCollection AddOpenFeature(this IServiceCollection services, Action configure) - { - Guard.ThrowIfNull(services); - Guard.ThrowIfNull(configure); - - // Register core OpenFeature services as singletons. - var api = new Api(); - Api.SetInstance(api); - services.TryAddSingleton(api); - services.TryAddSingleton(); - - var builder = new OpenFeatureBuilder(services); - configure(builder); - - // If a default provider is specified without additional providers, - // return early as no extra configuration is needed. - if (builder.HasDefaultProvider && builder.DomainBoundProviderRegistrationCount == 0) - { - return services; - } - - // Validate builder configuration to ensure consistency and required setup. - builder.Validate(); - - if (!builder.IsPolicyConfigured) - { - // Add a default name selector policy to use the first registered provider name as the default. - builder.AddPolicyName(options => - { - options.DefaultNameSelector = provider => - { - var options = provider.GetRequiredService>().Value; - return options.ProviderNames.First(); - }; - }); - } - - builder.AddPolicyBasedClient(); - return services; - } -} diff --git a/src/OpenFeature.DependencyInjection/PolicyNameOptions.cs b/src/OpenFeature.DependencyInjection/PolicyNameOptions.cs deleted file mode 100644 index f77b019b1..000000000 --- a/src/OpenFeature.DependencyInjection/PolicyNameOptions.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace OpenFeature.DependencyInjection; - -/// -/// Options to configure the default feature client name. -/// -public class PolicyNameOptions -{ - /// - /// A delegate to select the default feature client name. - /// - public Func DefaultNameSelector { get; set; } = null!; -} diff --git a/src/OpenFeature.DependencyInjection/Providers/Memory/FeatureBuilderExtensions.cs b/src/OpenFeature.DependencyInjection/Providers/Memory/FeatureBuilderExtensions.cs deleted file mode 100644 index d6346ad78..000000000 --- a/src/OpenFeature.DependencyInjection/Providers/Memory/FeatureBuilderExtensions.cs +++ /dev/null @@ -1,126 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using OpenFeature.Providers.Memory; - -namespace OpenFeature.DependencyInjection.Providers.Memory; - -/// -/// Extension methods for configuring feature providers with . -/// -#if NET8_0_OR_GREATER -[System.Diagnostics.CodeAnalysis.Experimental(Diagnostics.FeatureCodes.NewDi)] -#endif -public static partial class FeatureBuilderExtensions -{ - /// - /// Adds an in-memory feature provider to the with a factory for flags. - /// - /// 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) - => builder.AddProvider(provider => - { - var flags = flagsFactory(provider); - if (flags == null) - { - return new InMemoryProvider(); - } - - return new InMemoryProvider(flags); - }); - - /// - /// Adds an in-memory feature provider to the with a domain and factory for flags. - /// - /// 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) - => AddInMemoryProvider(builder, domain, (provider, _) => flagsFactory(provider)); - - /// - /// 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 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) - => builder.AddProvider(domain, (provider, key) => - { - var flags = flagsFactory(provider, key); - if (flags == null) - { - return new InMemoryProvider(); - } - - return new InMemoryProvider(flags); - }); - - /// - /// Adds an in-memory feature provider to the with optional flag configuration. - /// - /// 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)); - - /// - /// Adds an in-memory feature provider with a specific domain to the with optional flag configuration. - /// - /// 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)); - - private static FeatureProvider CreateProvider(IServiceProvider provider, string domain) - { - var options = provider.GetRequiredService>().Get(domain); - if (options.Flags == null) - { - return new InMemoryProvider(); - } - - return new InMemoryProvider(options.Flags); - } - - private static FeatureProvider CreateProvider(IServiceProvider provider) - { - var options = provider.GetRequiredService>().Value; - if (options.Flags == null) - { - return new InMemoryProvider(); - } - - return new InMemoryProvider(options.Flags); - } - - private static void ConfigureFlags(InMemoryProviderOptions options, Action>? configure) - { - if (configure != null) - { - options.Flags = new Dictionary(); - configure.Invoke(options.Flags); - } - } -} diff --git a/src/OpenFeature.DependencyInjection/Providers/Memory/InMemoryProviderOptions.cs b/src/OpenFeature.DependencyInjection/Providers/Memory/InMemoryProviderOptions.cs deleted file mode 100644 index ea5433f4e..000000000 --- a/src/OpenFeature.DependencyInjection/Providers/Memory/InMemoryProviderOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using OpenFeature.Providers.Memory; - -namespace OpenFeature.DependencyInjection.Providers.Memory; - -/// -/// Options for configuring the in-memory feature flag provider. -/// -public class InMemoryProviderOptions : OpenFeatureOptions -{ - /// - /// Gets or sets the feature flags to be used by the in-memory provider. - /// - /// - /// This property allows you to specify a dictionary of flags where the key is the flag name - /// and the value is the corresponding instance. - /// If no flags are provided, the in-memory provider will start with an empty set of flags. - /// - public IDictionary? Flags { get; set; } -} diff --git a/test/OpenFeature.AotCompatibility/OpenFeature.AotCompatibility.csproj b/test/OpenFeature.AotCompatibility/OpenFeature.AotCompatibility.csproj index d416bd75b..a3f5a6726 100644 --- a/test/OpenFeature.AotCompatibility/OpenFeature.AotCompatibility.csproj +++ b/test/OpenFeature.AotCompatibility/OpenFeature.AotCompatibility.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -17,8 +17,8 @@ + - diff --git a/test/OpenFeature.DependencyInjection.Tests/FeatureLifecycleManagerTests.cs b/test/OpenFeature.DependencyInjection.Tests/FeatureLifecycleManagerTests.cs deleted file mode 100644 index 8dc6a80bc..000000000 --- a/test/OpenFeature.DependencyInjection.Tests/FeatureLifecycleManagerTests.cs +++ /dev/null @@ -1,124 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging.Abstractions; -using OpenFeature.Constant; -using OpenFeature.DependencyInjection.Internal; -using OpenFeature.Model; - -namespace OpenFeature.DependencyInjection.Tests; - -public class FeatureLifecycleManagerTests -{ - private readonly IServiceCollection _serviceCollection; - - public FeatureLifecycleManagerTests() - { - Api.Instance.SetContext(null); - Api.Instance.ClearHooks(); - - _serviceCollection = new ServiceCollection() - .Configure(options => - { - options.AddDefaultProviderName(); - }); - } - - [Fact] - public async Task EnsureInitializedAsync_ShouldLogAndSetProvider_WhenProviderExists() - { - // Arrange - var featureProvider = new NoOpFeatureProvider(); - _serviceCollection.AddSingleton(featureProvider); - - var serviceProvider = _serviceCollection.BuildServiceProvider(); - var sut = new FeatureLifecycleManager(Api.Instance, serviceProvider, NullLogger.Instance); - - // Act - await sut.EnsureInitializedAsync().ConfigureAwait(true); - - // Assert - Assert.Equal(featureProvider, Api.Instance.GetProvider()); - } - - [Fact] - public async Task EnsureInitializedAsync_ShouldThrowException_WhenProviderDoesNotExist() - { - // Arrange - _serviceCollection.RemoveAll(); - - var serviceProvider = _serviceCollection.BuildServiceProvider(); - var sut = new FeatureLifecycleManager(Api.Instance, serviceProvider, NullLogger.Instance); - - // Act - var act = () => sut.EnsureInitializedAsync().AsTask(); - - // Assert - var exception = await Assert.ThrowsAsync(act).ConfigureAwait(true); - Assert.NotNull(exception); - Assert.False(string.IsNullOrWhiteSpace(exception.Message)); - } - - [Fact] - public async Task EnsureInitializedAsync_ShouldSetHook_WhenHooksAreRegistered() - { - // Arrange - var featureProvider = new NoOpFeatureProvider(); - var hook = new NoOpHook(); - - _serviceCollection.AddSingleton(featureProvider) - .AddKeyedSingleton("NoOpHook", (_, key) => hook) - .Configure(options => - { - options.AddHookName("NoOpHook"); - }); - - var serviceProvider = _serviceCollection.BuildServiceProvider(); - var sut = new FeatureLifecycleManager(Api.Instance, serviceProvider, NullLogger.Instance); - - // Act - await sut.EnsureInitializedAsync().ConfigureAwait(true); - - // Assert - var actual = Api.Instance.GetHooks().FirstOrDefault(); - Assert.Equal(hook, actual); - } - - [Fact] - public async Task EnsureInitializedAsync_ShouldSetHandler_WhenHandlersAreRegistered() - { - // Arrange - EventHandlerDelegate eventHandlerDelegate = (_) => { }; - var featureProvider = new NoOpFeatureProvider(); - var handler = new EventHandlerDelegateWrapper(ProviderEventTypes.ProviderReady, eventHandlerDelegate); - - _serviceCollection.AddSingleton(featureProvider) - .AddSingleton(_ => handler); - - var serviceProvider = _serviceCollection.BuildServiceProvider(); - var sut = new FeatureLifecycleManager(Api.Instance, serviceProvider, NullLogger.Instance); - - // Act - await sut.EnsureInitializedAsync().ConfigureAwait(true); - } - - [Fact] - public async Task EnsureInitializedAsync_ShouldSetHandler_WhenMultipleHandlersAreRegistered() - { - // Arrange - EventHandlerDelegate eventHandlerDelegate1 = (_) => { }; - EventHandlerDelegate eventHandlerDelegate2 = (_) => { }; - var featureProvider = new NoOpFeatureProvider(); - var handler1 = new EventHandlerDelegateWrapper(ProviderEventTypes.ProviderReady, eventHandlerDelegate1); - var handler2 = new EventHandlerDelegateWrapper(ProviderEventTypes.ProviderReady, eventHandlerDelegate2); - - _serviceCollection.AddSingleton(featureProvider) - .AddSingleton(_ => handler1) - .AddSingleton(_ => handler2); - - var serviceProvider = _serviceCollection.BuildServiceProvider(); - var sut = new FeatureLifecycleManager(Api.Instance, serviceProvider, NullLogger.Instance); - - // Act - await sut.EnsureInitializedAsync().ConfigureAwait(true); - } -} diff --git a/test/OpenFeature.DependencyInjection.Tests/NoOpFeatureProvider.cs b/test/OpenFeature.DependencyInjection.Tests/NoOpFeatureProvider.cs deleted file mode 100644 index ac3e52096..000000000 --- a/test/OpenFeature.DependencyInjection.Tests/NoOpFeatureProvider.cs +++ /dev/null @@ -1,52 +0,0 @@ -using OpenFeature.Model; - -namespace OpenFeature.DependencyInjection.Tests; - -// This class replicates the NoOpFeatureProvider implementation from src/OpenFeature/NoOpFeatureProvider.cs. -// It is used here to facilitate unit testing without relying on the internal NoOpFeatureProvider class. -// If the InternalsVisibleTo attribute is added to the OpenFeature project, -// this class can be removed and the original NoOpFeatureProvider can be directly accessed for testing. -internal sealed class NoOpFeatureProvider : FeatureProvider -{ - private readonly Metadata _metadata = new Metadata(NoOpProvider.NoOpProviderName); - - public override Metadata GetMetadata() - { - return this._metadata; - } - - public override Task> ResolveBooleanValueAsync(string flagKey, bool defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default) - { - return Task.FromResult(NoOpResponse(flagKey, defaultValue)); - } - - public override Task> ResolveStringValueAsync(string flagKey, string defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default) - { - return Task.FromResult(NoOpResponse(flagKey, defaultValue)); - } - - public override Task> ResolveIntegerValueAsync(string flagKey, int defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default) - { - return Task.FromResult(NoOpResponse(flagKey, defaultValue)); - } - - public override Task> ResolveDoubleValueAsync(string flagKey, double defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default) - { - return Task.FromResult(NoOpResponse(flagKey, defaultValue)); - } - - public override Task> ResolveStructureValueAsync(string flagKey, Value defaultValue, EvaluationContext? context = null, CancellationToken cancellationToken = default) - { - return Task.FromResult(NoOpResponse(flagKey, defaultValue)); - } - - private static ResolutionDetails NoOpResponse(string flagKey, T defaultValue) - { - return new ResolutionDetails( - flagKey, - defaultValue, - reason: NoOpProvider.ReasonNoOp, - variant: NoOpProvider.Variant - ); - } -} diff --git a/test/OpenFeature.DependencyInjection.Tests/NoOpHook.cs b/test/OpenFeature.DependencyInjection.Tests/NoOpHook.cs deleted file mode 100644 index cee6ef1df..000000000 --- a/test/OpenFeature.DependencyInjection.Tests/NoOpHook.cs +++ /dev/null @@ -1,26 +0,0 @@ -using OpenFeature.Model; - -namespace OpenFeature.DependencyInjection.Tests; - -internal class NoOpHook : Hook -{ - public override ValueTask BeforeAsync(HookContext context, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default) - { - return base.BeforeAsync(context, hints, cancellationToken); - } - - public override ValueTask AfterAsync(HookContext context, FlagEvaluationDetails details, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default) - { - return base.AfterAsync(context, details, hints, cancellationToken); - } - - public override ValueTask FinallyAsync(HookContext context, FlagEvaluationDetails evaluationDetails, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default) - { - return base.FinallyAsync(context, evaluationDetails, hints, cancellationToken); - } - - public override ValueTask ErrorAsync(HookContext context, Exception error, IReadOnlyDictionary? hints = null, CancellationToken cancellationToken = default) - { - return base.ErrorAsync(context, error, hints, cancellationToken); - } -} diff --git a/test/OpenFeature.DependencyInjection.Tests/NoOpProvider.cs b/test/OpenFeature.DependencyInjection.Tests/NoOpProvider.cs deleted file mode 100644 index 7bf20bcac..000000000 --- a/test/OpenFeature.DependencyInjection.Tests/NoOpProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace OpenFeature.DependencyInjection.Tests; - -internal static class NoOpProvider -{ - public const string NoOpProviderName = "No-op Provider"; - public const string ReasonNoOp = "No-op"; - public const string Variant = "No-op"; -} diff --git a/test/OpenFeature.DependencyInjection.Tests/OpenFeature.DependencyInjection.Tests.csproj b/test/OpenFeature.DependencyInjection.Tests/OpenFeature.DependencyInjection.Tests.csproj deleted file mode 100644 index d6bce29e8..000000000 --- a/test/OpenFeature.DependencyInjection.Tests/OpenFeature.DependencyInjection.Tests.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - net8.0;net9.0 - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - diff --git a/test/OpenFeature.DependencyInjection.Tests/OpenFeatureBuilderExtensionsTests.cs b/test/OpenFeature.DependencyInjection.Tests/OpenFeatureBuilderExtensionsTests.cs deleted file mode 100644 index f7cce0dfc..000000000 --- a/test/OpenFeature.DependencyInjection.Tests/OpenFeatureBuilderExtensionsTests.cs +++ /dev/null @@ -1,392 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using OpenFeature.DependencyInjection.Internal; -using OpenFeature.Model; - -namespace OpenFeature.DependencyInjection.Tests; - -public partial class OpenFeatureBuilderExtensionsTests -{ - private readonly IServiceCollection _services; - private readonly OpenFeatureBuilder _systemUnderTest; - - public OpenFeatureBuilderExtensionsTests() - { - _services = new ServiceCollection(); - _systemUnderTest = new OpenFeatureBuilder(_services); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void AddContext_Delegate_ShouldAddServiceToCollection(bool useServiceProviderDelegate) - { - // Act - var featureBuilder = useServiceProviderDelegate ? - _systemUnderTest.AddContext(_ => { }) : - _systemUnderTest.AddContext((_, _) => { }); - - // Assert - Assert.Equal(_systemUnderTest, featureBuilder); - Assert.True(_systemUnderTest.IsContextConfigured, "The context should be configured."); - Assert.Single(_services, serviceDescriptor => - serviceDescriptor.ServiceType == typeof(EvaluationContext) && - serviceDescriptor.Lifetime == ServiceLifetime.Transient); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void AddContext_Delegate_ShouldCorrectlyHandles(bool useServiceProviderDelegate) - { - // Arrange - bool delegateCalled = false; - - _ = useServiceProviderDelegate ? - _systemUnderTest.AddContext(_ => delegateCalled = true) : - _systemUnderTest.AddContext((_, _) => delegateCalled = true); - - var serviceProvider = _services.BuildServiceProvider(); - - // Act - var context = serviceProvider.GetService(); - - // Assert - Assert.True(_systemUnderTest.IsContextConfigured, "The context should be configured."); - Assert.NotNull(context); - Assert.True(delegateCalled, "The delegate should be invoked."); - } - -#if NET8_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.Experimental(Diagnostics.FeatureCodes.NewDi)] -#endif - [Theory] - [InlineData(1, true, 0)] - [InlineData(2, false, 1)] - [InlineData(3, true, 0)] - [InlineData(4, false, 1)] - public void AddProvider_ShouldAddProviderToCollection(int providerRegistrationType, bool expectsDefaultProvider, int expectsDomainBoundProvider) - { - // Act - var featureBuilder = providerRegistrationType switch - { - 1 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider()), - 2 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider()), - 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), - 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), - _ => throw new InvalidOperationException("Invalid mode.") - }; - - // Assert - Assert.False(_systemUnderTest.IsContextConfigured, "The context should not be configured."); - Assert.Equal(expectsDefaultProvider, _systemUnderTest.HasDefaultProvider); - Assert.False(_systemUnderTest.IsPolicyConfigured, "The policy should not be configured."); - Assert.Equal(expectsDomainBoundProvider, _systemUnderTest.DomainBoundProviderRegistrationCount); - Assert.Equal(_systemUnderTest, featureBuilder); - Assert.Single(_services, serviceDescriptor => - serviceDescriptor.ServiceType == typeof(FeatureProvider) && - serviceDescriptor.Lifetime == ServiceLifetime.Transient); - } - - class TestOptions : OpenFeatureOptions { } - -#if NET8_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.Experimental(Diagnostics.FeatureCodes.NewDi)] -#endif - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - public void AddProvider_ShouldResolveCorrectProvider(int providerRegistrationType) - { - // Arrange - _ = providerRegistrationType switch - { - 1 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider()), - 2 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider()), - 3 => _systemUnderTest.AddProvider(_ => new NoOpFeatureProvider(), o => { }), - 4 => _systemUnderTest.AddProvider("test", (_, _) => new NoOpFeatureProvider(), o => { }), - _ => throw new InvalidOperationException("Invalid mode.") - }; - - var serviceProvider = _services.BuildServiceProvider(); - - // Act - var provider = providerRegistrationType switch - { - 1 or 3 => serviceProvider.GetService(), - 2 or 4 => serviceProvider.GetKeyedService("test"), - _ => throw new InvalidOperationException("Invalid mode.") - }; - - // Assert - Assert.NotNull(provider); - Assert.IsType(provider); - } - - [Theory] - [InlineData(1, true, 1)] - [InlineData(2, true, 1)] - [InlineData(3, false, 2)] - [InlineData(4, true, 1)] - [InlineData(5, true, 1)] - [InlineData(6, false, 2)] - [InlineData(7, true, 2)] - [InlineData(8, true, 2)] - public void AddProvider_VerifiesDefaultAndDomainBoundProvidersBasedOnConfiguration(int providerRegistrationType, bool expectsDefaultProvider, int expectsDomainBoundProvider) - { - // Act - var featureBuilder = providerRegistrationType switch - { - 1 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider()) - .AddProvider("test", (_, _) => new NoOpFeatureProvider()), - 2 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider()) - .AddProvider("test", (_, _) => new NoOpFeatureProvider()), - 3 => _systemUnderTest - .AddProvider("test1", (_, _) => new NoOpFeatureProvider()) - .AddProvider("test2", (_, _) => new NoOpFeatureProvider()), - 4 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) - .AddProvider("test", (_, _) => new NoOpFeatureProvider()), - 5 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) - .AddProvider("test", (_, _) => new NoOpFeatureProvider()), - 6 => _systemUnderTest - .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 => { }), - _ => throw new InvalidOperationException("Invalid mode.") - }; - - // Assert - Assert.False(_systemUnderTest.IsContextConfigured, "The context should not be configured."); - Assert.Equal(expectsDefaultProvider, _systemUnderTest.HasDefaultProvider); - Assert.False(_systemUnderTest.IsPolicyConfigured, "The policy should not be configured."); - Assert.Equal(expectsDomainBoundProvider, _systemUnderTest.DomainBoundProviderRegistrationCount); - Assert.Equal(_systemUnderTest, featureBuilder); - } - - [Theory] - [InlineData(1, null)] - [InlineData(2, "test")] - [InlineData(3, "test2")] - [InlineData(4, "test")] - [InlineData(5, null)] - [InlineData(6, "test1")] - [InlineData(7, "test2")] - [InlineData(8, null)] - public void AddProvider_ConfiguresPolicyNameAcrossMultipleProviderSetups(int providerRegistrationType, string? policyName) - { - // Arrange - var featureBuilder = providerRegistrationType switch - { - 1 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider()) - .AddProvider("test", (_, _) => new NoOpFeatureProvider()) - .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), - 2 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider()) - .AddProvider("test", (_, _) => new NoOpFeatureProvider()) - .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), - 3 => _systemUnderTest - .AddProvider("test1", (_, _) => new NoOpFeatureProvider()) - .AddProvider("test2", (_, _) => new NoOpFeatureProvider()) - .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), - 4 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) - .AddProvider("test", (_, _) => new NoOpFeatureProvider()) - .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), - 5 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider(), o => { }) - .AddProvider("test", (_, _) => new NoOpFeatureProvider()) - .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), - 6 => _systemUnderTest - .AddProvider("test1", (_, _) => new NoOpFeatureProvider(), o => { }) - .AddProvider("test2", (_, _) => new NoOpFeatureProvider()) - .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), - 7 => _systemUnderTest - .AddProvider(_ => new NoOpFeatureProvider()) - .AddProvider("test", (_, _) => new NoOpFeatureProvider()) - .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 => { }) - .AddPolicyName(policy => policy.DefaultNameSelector = provider => policyName), - _ => throw new InvalidOperationException("Invalid mode.") - }; - - var serviceProvider = _services.BuildServiceProvider(); - - // Act - var policy = serviceProvider.GetRequiredService>().Value; - var name = policy.DefaultNameSelector(serviceProvider); - var provider = name == null ? - serviceProvider.GetService() : - serviceProvider.GetRequiredKeyedService(name); - - // Assert - Assert.True(featureBuilder.IsPolicyConfigured, "The policy should be configured."); - Assert.NotNull(provider); - Assert.IsType(provider); - } - - [Fact] - public void AddHook_AddsHookAsKeyedService() - { - // Arrange - _systemUnderTest.AddHook(); - - var serviceProvider = _services.BuildServiceProvider(); - - // Act - var hook = serviceProvider.GetKeyedService("NoOpHook"); - - // Assert - Assert.NotNull(hook); - } - - [Fact] - public void AddHook_AddsHookNameToOpenFeatureOptions() - { - // Arrange - _systemUnderTest.AddHook(sp => new NoOpHook()); - - var serviceProvider = _services.BuildServiceProvider(); - - // Act - var options = serviceProvider.GetRequiredService>(); - - // Assert - Assert.Contains(options.Value.HookNames, t => t == "NoOpHook"); - } - - [Fact] - public void AddHook_WithSpecifiedNameToOpenFeatureOptions() - { - // Arrange - _systemUnderTest.AddHook("my-custom-name"); - - var serviceProvider = _services.BuildServiceProvider(); - - // Act - var hook = serviceProvider.GetKeyedService("my-custom-name"); - - // Assert - Assert.NotNull(hook); - } - - [Fact] - public void AddHook_WithSpecifiedNameAndImplementationFactory_AsKeyedService() - { - // Arrange - _systemUnderTest.AddHook("my-custom-name", (serviceProvider) => new NoOpHook()); - - var serviceProvider = _services.BuildServiceProvider(); - - // Act - var hook = serviceProvider.GetKeyedService("my-custom-name"); - - // Assert - Assert.NotNull(hook); - } - - [Fact] - public void AddHook_WithInstance_AddsHookAsKeyedService() - { - // Arrange - var expectedHook = new NoOpHook(); - _systemUnderTest.AddHook(expectedHook); - - var serviceProvider = _services.BuildServiceProvider(); - - // Act - var actualHook = serviceProvider.GetKeyedService("NoOpHook"); - - // Assert - Assert.NotNull(actualHook); - Assert.Equal(expectedHook, actualHook); - } - - [Fact] - public void AddHook_WithSpecifiedNameAndInstance_AddsHookAsKeyedService() - { - // Arrange - var expectedHook = new NoOpHook(); - _systemUnderTest.AddHook("custom-hook", expectedHook); - - var serviceProvider = _services.BuildServiceProvider(); - - // Act - var actualHook = serviceProvider.GetKeyedService("custom-hook"); - - // Assert - Assert.NotNull(actualHook); - Assert.Equal(expectedHook, actualHook); - } - - [Fact] - public void AddHandler_AddsEventHandlerDelegateWrapperAsKeyedService() - { - // Arrange - EventHandlerDelegate eventHandler = (eventDetails) => { }; - _systemUnderTest.AddHandler(Constant.ProviderEventTypes.ProviderReady, eventHandler); - - var serviceProvider = _services.BuildServiceProvider(); - - // Act - var handler = serviceProvider.GetService(); - - // Assert - Assert.NotNull(handler); - Assert.Equal(eventHandler, handler.EventHandlerDelegate); - } - - [Fact] - public void AddHandlerTwice_MultipleEventHandlerDelegateWrappersAsKeyedServices() - { - // Arrange - EventHandlerDelegate eventHandler1 = (eventDetails) => { }; - EventHandlerDelegate eventHandler2 = (eventDetails) => { }; - _systemUnderTest.AddHandler(Constant.ProviderEventTypes.ProviderReady, eventHandler1); - _systemUnderTest.AddHandler(Constant.ProviderEventTypes.ProviderReady, eventHandler2); - - var serviceProvider = _services.BuildServiceProvider(); - - // Act - var handler = serviceProvider.GetServices(); - - // Assert - Assert.NotEmpty(handler); - Assert.Equal(eventHandler1, handler.ElementAt(0).EventHandlerDelegate); - Assert.Equal(eventHandler2, handler.ElementAt(1).EventHandlerDelegate); - } - - [Fact] - public void AddHandler_WithImplementationFactory_AddsEventHandlerDelegateWrapperAsKeyedService() - { - // Arrange - EventHandlerDelegate eventHandler = (eventDetails) => { }; - _systemUnderTest.AddHandler(Constant.ProviderEventTypes.ProviderReady, _ => eventHandler); - - var serviceProvider = _services.BuildServiceProvider(); - - // Act - var handler = serviceProvider.GetService(); - - // Assert - Assert.NotNull(handler); - Assert.Equal(eventHandler, handler.EventHandlerDelegate); - } -} diff --git a/test/OpenFeature.DependencyInjection.Tests/OpenFeatureServiceCollectionExtensionsTests.cs b/test/OpenFeature.DependencyInjection.Tests/OpenFeatureServiceCollectionExtensionsTests.cs deleted file mode 100644 index ddda3f224..000000000 --- a/test/OpenFeature.DependencyInjection.Tests/OpenFeatureServiceCollectionExtensionsTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using NSubstitute; - -namespace OpenFeature.DependencyInjection.Tests; - -public class OpenFeatureServiceCollectionExtensionsTests -{ - private readonly IServiceCollection _systemUnderTest; - private readonly Action _configureAction; - - public OpenFeatureServiceCollectionExtensionsTests() - { - _systemUnderTest = new ServiceCollection(); - _configureAction = Substitute.For>(); - } - - [Fact] - public void AddOpenFeature_ShouldRegisterApiInstanceAndLifecycleManagerAsSingleton() - { - // Act - _systemUnderTest.AddOpenFeature(_configureAction); - - Assert.Single(_systemUnderTest, s => s.ServiceType == typeof(Api) && s.Lifetime == ServiceLifetime.Singleton); - Assert.Single(_systemUnderTest, s => s.ServiceType == typeof(IFeatureLifecycleManager) && s.Lifetime == ServiceLifetime.Singleton); - Assert.Single(_systemUnderTest, s => s.ServiceType == typeof(IFeatureClient) && s.Lifetime == ServiceLifetime.Scoped); - } - - [Fact] - public void AddOpenFeature_ShouldInvokeConfigureAction() - { - // Act - _systemUnderTest.AddOpenFeature(_configureAction); - - // Assert - _configureAction.Received(1).Invoke(Arg.Any()); - } -}