flux-limiter 0.8.3

A rate limiter based on the Generic Cell Rate Algorithm (GCRA).
Documentation
# Component Design

Detailed exploration of Flux Limiter's core components and their design rationale.

## FluxLimiter<T, C>

The main rate limiter struct uses generics for flexibility.

### Structure Definition

```rust
#[derive(Debug)]
pub struct FluxLimiter<T, C = SystemClock>
where
    T: Hash + Eq + Clone,  // Client identifier type
    C: Clock,              // Time source
{
    rate_nanos: u64,                    // Rate interval in nanoseconds
    tolerance_nanos: u64,               // Burst tolerance in nanoseconds
    client_state: Arc<DashMap<T, u64>>, // Client TAT storage
    clock: C,                           // Time abstraction
}
```

**Implemented Traits**:
- `Debug` — via `#[derive]`
- `Clone` — manual implementation; requires `C: Clone`. Cloning shares the same `DashMap` via `Arc::clone`, so both the original and clone operate on the same client state.

### Design Rationale

**Generic Client ID (T)**:
- Supports `String`, `IpAddr`, `u64`, custom types
- Constrains: `Hash + Eq + Clone`
- Zero-cost abstraction - no runtime overhead

**Generic Clock (C)**:
- Default: `SystemClock` for production
- Alternative: `TestClock` for testing
- Custom: User-defined time sources
- Constrains: `Clock` trait

**Arc<DashMap>**:
- Thread-safe shared ownership
- Lock-free concurrent access
- Minimal contention
- Efficient cloning

**Nanosecond Storage**:
- `u64` for rate and tolerance
- Maintains precision throughout calculations
- Avoids floating-point arithmetic in hot path

### Public API

```rust
impl<T, C> FluxLimiter<T, C>
where
    T: Hash + Eq + Clone,
    C: Clock,
{
    pub fn with_config(
        config: FluxLimiterConfig,
        clock: C,
    ) -> Result<Self, FluxLimiterError>

    pub fn check_request(
        &self,
        client_id: T,
    ) -> Result<FluxLimiterDecision, FluxLimiterError>

    pub fn cleanup_stale_clients(
        &self,
        max_stale_nanos: u64,
    ) -> Result<(), FluxLimiterError>

    pub fn rate(&self) -> f64
    pub fn burst(&self) -> f64
}
```

## FluxLimiterConfig

Configuration management with builder pattern support.

### Structure Definition

```rust
#[derive(Debug, Clone)]
pub struct FluxLimiterConfig {
    rate_per_second: f64,   // User-friendly rate specification
    burst_capacity: f64,    // User-friendly burst specification
}
```

### Design Rationale

**User-Friendly Units**:
- `rate_per_second`: Intuitive "requests per second"
- `burst_capacity`: Additional burst allowance
- Converted to nanoseconds internally

**Builder Pattern**:
```rust
impl FluxLimiterConfig {
    pub fn new(rate_per_second: f64, burst_capacity: f64) -> Self
    pub fn rate(mut self, rate_per_second: f64) -> Self
    pub fn burst(mut self, burst_capacity: f64) -> Self
    pub fn validate(&self) -> Result<(), FluxLimiterError>
}
```

### Validation

```rust
pub fn validate(&self) -> Result<(), FluxLimiterError> {
    if self.rate_per_second <= 0.0 {
        return Err(FluxLimiterError::InvalidRate);
    }
    if self.burst_capacity < 0.0 {
        return Err(FluxLimiterError::InvalidBurst);
    }
    Ok(())
}
```

**Validation Rules**:
- Rate must be positive (> 0.0)
- Burst must be non-negative (≥ 0.0)
- Checked at construction time

## FluxLimiterDecision

Rich metadata returned from rate limiting decisions.

### Structure Definition

```rust
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct FluxLimiterDecision {
    pub allowed: bool,                    // Primary decision
    pub retry_after_seconds: Option<f64>, // When to retry (if denied)
    pub remaining_capacity: Option<f64>,  // Remaining burst capacity
    pub reset_time_nanos: u64,           // When window resets
}
```

The `#[non_exhaustive]` attribute ensures that adding fields in future versions
is not a breaking change. Consumers can still access all public fields via dot
notation, but cannot construct or destructure the struct exhaustively. This is
consistent with `FluxLimiterError`, which is also `#[non_exhaustive]`.

### Design Rationale

**Rich Metadata Enables**:
- HTTP rate limit headers (X-RateLimit-Remaining, Retry-After)
- Client-side backoff strategies
- Monitoring and observability
- Debugging and diagnostics

**Field Details**:

1. **`allowed: bool`**
   - Primary decision
   - `true` = allow request, `false` = deny

2. **`retry_after_seconds: Option<f64>`**
   - `Some(seconds)` when denied
   - How long to wait before retrying
   - Used for HTTP `Retry-After` header

3. **`remaining_capacity: Option<f64>`**
   - Current burst capacity remaining
   - Useful for `X-RateLimit-Remaining` header
   - Helps clients understand their quota

4. **`reset_time_nanos: u64`**
   - Nanosecond timestamp when limit resets
   - Convert to HTTP `X-RateLimit-Reset` header
   - Absolute time, not relative

## FluxLimiterError

Comprehensive error handling for robust production usage.

### Enum Definition

```rust
#[non_exhaustive]
#[derive(Debug)]
pub enum FluxLimiterError {
    InvalidRate,           // Configuration: rate ≤ 0
    InvalidBurst,          // Configuration: burst < 0
    ClockError(ClockError), // Runtime: clock failure
}

#[derive(Debug)]
pub enum ClockError {
    SystemTimeError,       // System time unavailable
}
```

### Design Rationale

**Explicit Error Types**:
- Configuration errors (InvalidRate, InvalidBurst)
- Runtime errors (ClockError)
- Clear separation of error categories

**Error Display**:
```rust
impl std::fmt::Display for FluxLimiterError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            Self::InvalidRate => write!(f, "Rate must be positive"),
            Self::InvalidBurst => write!(f, "Burst must be non-negative"),
            Self::ClockError(err) => write!(f, "Clock error occurred: {}", err),
        }
    }
}
```

## Clock Trait

Abstraction for pluggable time sources.

### Trait Definition

```rust
pub trait Clock: Send + Sync {
    fn now(&self) -> Result<u64, ClockError>;
}
```

### Design Rationale

**Benefits**:
- Enables deterministic testing
- Handles real-world clock issues
- Allows custom time sources
- Graceful error handling

**Send + Sync Requirements**:
- Thread-safe: Can be shared across threads
- Required for concurrent rate limiting

### SystemClock

Production time source using system time:

```rust
#[derive(Debug, Clone)]
pub struct SystemClock;

impl Clock for SystemClock {
    fn now(&self) -> Result<u64, ClockError> {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map(|d| d.as_nanos() as u64)
            .map_err(|_| ClockError::SystemTimeError)
    }
}
```

**Handles Real-World Issues**:
- System clock going backwards (NTP adjustments)
- Clock resolution limitations
- System suspend/resume
- Virtualization time skips

### TestClock

Deterministic time source for testing:

```rust
pub struct TestClock {
    time: Arc<AtomicU64>,        // Current time in nanoseconds
    should_fail: Arc<AtomicBool>, // Failure simulation flag
}

impl TestClock {
    pub fn new(initial_time_secs: f64) -> Self
    pub fn advance(&self, duration_secs: f64)
    pub fn set_time(&self, time_secs: f64)
    pub fn fail_next_call(&self)
}
```

**Features**:
- Precise time control
- Failure simulation
- Thread-safe
- Deterministic testing

## Memory Layout

### FluxLimiter Size

```rust
FluxLimiter<String, SystemClock> {
    rate_nanos: u64,         // 8 bytes
    tolerance_nanos: u64,    // 8 bytes
    client_state: Arc<..>,   // 8 bytes (pointer)
    clock: SystemClock,      // 0 bytes (zero-sized type)
}
// Total: 24 bytes
```

**Cache Efficiency**:
- Small struct size fits in cache line
- Frequently accessed fields grouped
- Arc enables cheap cloning

### Per-Client State

```rust
DashMap<String, u64> entry:
    String: ~24 bytes (pointer + len + capacity)
    u64:    8 bytes
    Overhead: ~16 bytes (hash map metadata)
// Total per client: ~48 bytes
```

### Scalability

```
1,000 clients     = ~48 KB
10,000 clients    = ~480 KB
100,000 clients   = ~4.8 MB
1,000,000 clients = ~48 MB
```

## Thread Safety

All components are thread-safe:

- **FluxLimiter**: Safe to share via `Arc`
- **DashMap**: Lock-free concurrent access
- **Clock**: `Send + Sync` requirement
- **Decisions**: Immutable after creation

## Next Steps

- [Concurrency Model]./concurrency.md - Understand thread safety
- [Performance Design]./performance.md - Optimization techniques
- [Error Handling Architecture]./error-handling.md - Error strategies