// -----------------------------------------------------------------------------
// 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; }
}