// ----------------------------------------------------------------------------- // RateLimitConfig.cs // Sprint: SPRINT_1200_001_001_router_rate_limiting_core // Task: 1.1 - Rate Limit Configuration Models // Description: Root configuration class with YAML binding support // ----------------------------------------------------------------------------- using Microsoft.Extensions.Configuration; namespace StellaOps.Router.Gateway.RateLimit; /// /// Root configuration for Router rate limiting. /// Per advisory "Designing 202 + Retry-After Backpressure Control". /// public sealed class RateLimitConfig { /// /// Activation gate: only check Valkey when traffic exceeds this threshold per 5 minutes. /// Set to 0 to always check Valkey. Default: 5000. /// [ConfigurationKeyName("process_back_pressure_when_more_than_per_5min")] public int ActivationThresholdPer5Min { get; set; } = 5000; /// /// Instance-level rate limits (in-memory, per router instance). /// [ConfigurationKeyName("for_instance")] public InstanceLimitsConfig? ForInstance { get; set; } /// /// Environment-level rate limits (Valkey-backed, across all router instances). /// [ConfigurationKeyName("for_environment")] public EnvironmentLimitsConfig? ForEnvironment { get; set; } /// /// Typo alias support for backwards compatibility. /// [ConfigurationKeyName("back_pressure_limtis")] public RateLimitsSection? BackPressureLimtis { get; set; } /// /// Load configuration from IConfiguration. /// public static RateLimitConfig Load(IConfiguration configuration) { var config = new RateLimitConfig(); configuration.Bind("rate_limiting", config); return config.Validate(); } /// /// Validate configuration values. /// public RateLimitConfig Validate() { if (ActivationThresholdPer5Min < 0) throw new ArgumentException("Activation threshold must be >= 0", nameof(ActivationThresholdPer5Min)); ForInstance?.Validate("for_instance"); ForEnvironment?.Validate("for_environment"); return this; } /// /// Whether rate limiting is enabled (at least one scope configured). /// public bool IsEnabled => ForInstance is not null || ForEnvironment is not null; } /// /// Instance-level rate limit configuration (in-memory). /// public sealed class InstanceLimitsConfig { /// Time window in seconds. [ConfigurationKeyName("per_seconds")] public int PerSeconds { get; set; } /// Maximum requests in the time window. [ConfigurationKeyName("max_requests")] public int MaxRequests { get; set; } /// Burst window in seconds. [ConfigurationKeyName("allow_burst_for_seconds")] public int AllowBurstForSeconds { get; set; } /// Maximum burst requests. [ConfigurationKeyName("allow_max_burst_requests")] public int AllowMaxBurstRequests { get; set; } /// Typo alias for backwards compatibility. [ConfigurationKeyName("allow_max_bust_requests")] public int AllowMaxBustRequests { get; set; } /// /// Validate configuration. /// public void Validate(string path) { if (PerSeconds < 0 || MaxRequests < 0) throw new ArgumentException($"{path}: Window (per_seconds) and limit (max_requests) must be >= 0"); if (AllowBurstForSeconds < 0 || AllowMaxBurstRequests < 0) throw new ArgumentException($"{path}: Burst window and limit must be >= 0"); // Normalize typo alias if (AllowMaxBustRequests > 0 && AllowMaxBurstRequests == 0) AllowMaxBurstRequests = AllowMaxBustRequests; } } /// /// Environment-level rate limit configuration (Valkey-backed). /// public sealed class EnvironmentLimitsConfig { /// Valkey connection string. [ConfigurationKeyName("valkey_connection")] public string ValkeyConnection { get; set; } = "localhost:6379"; /// Valkey bucket/prefix for rate limit keys. [ConfigurationKeyName("valkey_bucket")] public string ValkeyBucket { get; set; } = "stella-router-rate-limit"; /// Circuit breaker configuration. [ConfigurationKeyName("circuit_breaker")] public CircuitBreakerConfig? CircuitBreaker { get; set; } /// Time window in seconds. [ConfigurationKeyName("per_seconds")] public int PerSeconds { get; set; } /// Maximum requests in the time window. [ConfigurationKeyName("max_requests")] public int MaxRequests { get; set; } /// Burst window in seconds. [ConfigurationKeyName("allow_burst_for_seconds")] public int AllowBurstForSeconds { get; set; } /// Maximum burst requests. [ConfigurationKeyName("allow_max_burst_requests")] public int AllowMaxBurstRequests { get; set; } /// Per-microservice overrides. [ConfigurationKeyName("microservices")] public Dictionary? Microservices { get; set; } /// /// Validate configuration. /// public void Validate(string path) { if (string.IsNullOrWhiteSpace(ValkeyConnection)) throw new ArgumentException($"{path}: valkey_connection is required"); if (PerSeconds < 0 || MaxRequests < 0) throw new ArgumentException($"{path}: Window and limit must be >= 0"); CircuitBreaker?.Validate($"{path}.circuit_breaker"); if (Microservices is not null) { foreach (var (name, config) in Microservices) { config.Validate($"{path}.microservices.{name}"); } } } } /// /// Per-microservice rate limit overrides. /// public sealed class MicroserviceLimitsConfig { /// Time window in seconds. [ConfigurationKeyName("per_seconds")] public int PerSeconds { get; set; } /// Maximum requests in the time window. [ConfigurationKeyName("max_requests")] public int MaxRequests { get; set; } /// Burst window in seconds (optional). [ConfigurationKeyName("allow_burst_for_seconds")] public int? AllowBurstForSeconds { get; set; } /// Maximum burst requests (optional). [ConfigurationKeyName("allow_max_burst_requests")] public int? AllowMaxBurstRequests { get; set; } /// /// Validate configuration. /// public void Validate(string path) { if (PerSeconds < 0 || MaxRequests < 0) throw new ArgumentException($"{path}: Window and limit must be >= 0"); } } /// /// Circuit breaker configuration for Valkey resilience. /// public sealed class CircuitBreakerConfig { /// Number of failures before opening the circuit. [ConfigurationKeyName("failure_threshold")] public int FailureThreshold { get; set; } = 5; /// Seconds to keep circuit open. [ConfigurationKeyName("timeout_seconds")] public int TimeoutSeconds { get; set; } = 30; /// Seconds in half-open state before full reset. [ConfigurationKeyName("half_open_timeout")] public int HalfOpenTimeout { get; set; } = 10; /// /// Validate configuration. /// public void Validate(string path) { if (FailureThreshold < 1) throw new ArgumentException($"{path}: failure_threshold must be >= 1"); if (TimeoutSeconds < 1) throw new ArgumentException($"{path}: timeout_seconds must be >= 1"); if (HalfOpenTimeout < 1) throw new ArgumentException($"{path}: half_open_timeout must be >= 1"); } } /// /// Generic rate limits section (for typo alias support). /// public sealed class RateLimitsSection { [ConfigurationKeyName("per_seconds")] public int PerSeconds { get; set; } [ConfigurationKeyName("max_requests")] public int MaxRequests { get; set; } }