Files
git.stella-ops.org/src/__Libraries/StellaOps.FeatureFlags/FeatureFlagServiceCollectionExtensions.cs
2026-01-13 18:53:39 +02:00

177 lines
5.2 KiB
C#

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using StellaOps.FeatureFlags.Providers;
namespace StellaOps.FeatureFlags;
/// <summary>
/// Extension methods for configuring feature flag services.
/// </summary>
public static class FeatureFlagServiceCollectionExtensions
{
/// <summary>
/// Adds the feature flag service with default options.
/// </summary>
public static IServiceCollection AddFeatureFlags(
this IServiceCollection services)
{
return services.AddFeatureFlags(_ => { });
}
/// <summary>
/// Adds the feature flag service with configuration.
/// </summary>
public static IServiceCollection AddFeatureFlags(
this IServiceCollection services,
Action<FeatureFlagOptions> configure)
{
services.Configure(configure);
services.TryAddSingleton<IFeatureFlagService, CompositeFeatureFlagService>();
return services;
}
/// <summary>
/// Adds a configuration-based feature flag provider.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="sectionName">Configuration section name (default: "FeatureFlags").</param>
/// <param name="priority">Provider priority (lower = higher priority).</param>
public static IServiceCollection AddConfigurationFeatureFlags(
this IServiceCollection services,
string sectionName = "FeatureFlags",
int priority = 50)
{
services.AddSingleton<IFeatureFlagProvider>(sp =>
{
var configuration = sp.GetRequiredService<IConfiguration>();
return new ConfigurationFeatureFlagProvider(configuration, sectionName, priority);
});
return services;
}
/// <summary>
/// Adds a custom feature flag provider.
/// </summary>
public static IServiceCollection AddFeatureFlagProvider<TProvider>(
this IServiceCollection services)
where TProvider : class, IFeatureFlagProvider
{
services.AddSingleton<IFeatureFlagProvider, TProvider>();
return services;
}
/// <summary>
/// Adds a custom feature flag provider using a factory.
/// </summary>
public static IServiceCollection AddFeatureFlagProvider(
this IServiceCollection services,
Func<IServiceProvider, IFeatureFlagProvider> factory)
{
services.AddSingleton(factory);
return services;
}
/// <summary>
/// Adds an in-memory feature flag provider for testing.
/// </summary>
public static IServiceCollection AddInMemoryFeatureFlags(
this IServiceCollection services,
IDictionary<string, bool> flags,
int priority = 0)
{
services.AddSingleton<IFeatureFlagProvider>(
new InMemoryFeatureFlagProvider(flags, priority));
return services;
}
}
/// <summary>
/// In-memory feature flag provider for testing and overrides.
/// </summary>
public sealed class InMemoryFeatureFlagProvider : FeatureFlagProviderBase
{
private readonly Dictionary<string, bool> _flags;
private readonly Dictionary<string, object?> _variants;
/// <summary>
/// Creates a new in-memory provider with the specified flags.
/// </summary>
public InMemoryFeatureFlagProvider(
IDictionary<string, bool> flags,
int priority = 0)
{
_flags = new Dictionary<string, bool>(flags, StringComparer.OrdinalIgnoreCase);
_variants = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
Priority = priority;
}
/// <inheritdoc />
public override string Name => "InMemory";
/// <inheritdoc />
public override int Priority { get; }
/// <inheritdoc />
public override Task<FeatureFlagResult?> TryGetFlagAsync(
string flagKey,
FeatureFlagEvaluationContext context,
CancellationToken ct = default)
{
if (_flags.TryGetValue(flagKey, out var enabled))
{
_variants.TryGetValue(flagKey, out var variant);
return Task.FromResult<FeatureFlagResult?>(
CreateResult(flagKey, enabled, variant, "From in-memory provider"));
}
return Task.FromResult<FeatureFlagResult?>(null);
}
/// <inheritdoc />
public override Task<IReadOnlyList<FeatureFlagDefinition>> ListFlagsAsync(
CancellationToken ct = default)
{
var flags = _flags.Select(kvp => new FeatureFlagDefinition(
kvp.Key,
null,
kvp.Value,
kvp.Value)).ToList();
return Task.FromResult<IReadOnlyList<FeatureFlagDefinition>>(flags);
}
/// <summary>
/// Sets a flag value.
/// </summary>
public void SetFlag(string key, bool enabled, object? variant = null)
{
_flags[key] = enabled;
if (variant is not null)
{
_variants[key] = variant;
}
}
/// <summary>
/// Removes a flag.
/// </summary>
public void RemoveFlag(string key)
{
_flags.Remove(key);
_variants.Remove(key);
}
/// <summary>
/// Clears all flags.
/// </summary>
public void Clear()
{
_flags.Clear();
_variants.Clear();
}
}