flux-limiter 0.8.2

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

Comprehensive guide to handling errors in Flux Limiter.

## Error Types

Flux Limiter provides a well-defined error hierarchy:

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

pub enum ClockError {
    SystemTimeError,       // System time unavailable
}
```

## Configuration Errors

Configuration errors occur when creating a rate limiter with invalid settings.

### InvalidRate Error

```rust
use flux_limiter::{FluxLimiterConfig, FluxLimiter, SystemClock, FluxLimiterError};

// Invalid: rate must be positive
let config = FluxLimiterConfig::new(-10.0, 5.0);

match FluxLimiter::with_config(config, SystemClock) {
    Ok(_) => println!("Success"),
    Err(FluxLimiterError::InvalidRate) => {
        eprintln!("Error: Rate must be positive (> 0)");
    }
    Err(e) => eprintln!("Other error: {}", e),
}
```

### InvalidBurst Error

```rust
// Invalid: burst must be non-negative
let config = FluxLimiterConfig::new(10.0, -5.0);

match FluxLimiter::with_config(config, SystemClock) {
    Ok(_) => println!("Success"),
    Err(FluxLimiterError::InvalidBurst) => {
        eprintln!("Error: Burst must be non-negative (≥ 0)");
    }
    Err(e) => eprintln!("Other error: {}", e),
}
```

### Handling Configuration Errors

Configuration errors should be caught early, typically at application startup:

```rust
fn create_rate_limiter() -> Result<FluxLimiter<String, SystemClock>, String> {
    let config = FluxLimiterConfig::new(100.0, 50.0);

    FluxLimiter::with_config(config, SystemClock)
        .map_err(|e| match e {
            FluxLimiterError::InvalidRate => {
                "Invalid configuration: rate must be positive".to_string()
            }
            FluxLimiterError::InvalidBurst => {
                "Invalid configuration: burst must be non-negative".to_string()
            }
            _ => format!("Configuration error: {}", e),
        })
}

fn main() {
    let limiter = create_rate_limiter()
        .expect("Failed to create rate limiter with valid configuration");

    // Use limiter...
}
```

## Runtime Clock Errors

Clock errors can occur during normal operation when the system clock is unavailable or behaves unexpectedly.

### Understanding Clock Errors

Clock errors happen when:
- System time API fails
- Clock jumps backward (NTP adjustment)
- System suspend/resume causes time discontinuity
- Virtualization causes time skips

### Basic Clock Error Handling

```rust
match limiter.check_request("user_123") {
    Ok(decision) => {
        if decision.allowed {
            // Process request
        } else {
            // Rate limited
        }
    }
    Err(FluxLimiterError::ClockError(_)) => {
        eprintln!("System clock error detected");
        // Implement your error policy
    }
    Err(e) => {
        eprintln!("Unexpected error: {}", e);
    }
}
```

## Error Handling Policies

Different applications require different error handling strategies.

### Fail-Open Policy

Allow requests when the rate limiter encounters errors:

```rust
fn should_allow_request(
    limiter: &FluxLimiter<String, SystemClock>,
    client_id: &str
) -> bool {
    match limiter.check_request(client_id) {
        Ok(decision) => decision.allowed,
        Err(FluxLimiterError::ClockError(_)) => {
            // Fail-open: allow request on clock error
            eprintln!("Clock error - allowing request (fail-open policy)");
            true
        }
        Err(e) => {
            eprintln!("Rate limiter error: {} - allowing request", e);
            true
        }
    }
}
```

**Use when:**
- Availability is more important than strict rate limiting
- False positives (allowing too many requests) are acceptable
- Your backend can handle temporary spikes

### Fail-Closed Policy

Deny requests when the rate limiter encounters errors:

```rust
fn should_allow_request(
    limiter: &FluxLimiter<String, SystemClock>,
    client_id: &str
) -> bool {
    match limiter.check_request(client_id) {
        Ok(decision) => decision.allowed,
        Err(FluxLimiterError::ClockError(_)) => {
            // Fail-closed: deny request on clock error
            eprintln!("Clock error - denying request (fail-closed policy)");
            false
        }
        Err(e) => {
            eprintln!("Rate limiter error: {} - denying request", e);
            false
        }
    }
}
```

**Use when:**
- Security is paramount
- False negatives (denying legitimate requests) are acceptable
- Protecting backend from overload is critical

### Fallback Policy

Use alternative rate limiting when clock errors occur:

```rust
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;

struct FallbackRateLimiter {
    primary: FluxLimiter<String, SystemClock>,
    fallback_counter: Arc<AtomicU64>,
    fallback_limit: u64,
}

impl FallbackRateLimiter {
    fn check_request(&self, client_id: String) -> bool {
        match self.primary.check_request(client_id) {
            Ok(decision) => decision.allowed,
            Err(FluxLimiterError::ClockError(_)) => {
                // Use simple counter as fallback
                let count = self.fallback_counter.fetch_add(1, Ordering::Relaxed);

                if count >= self.fallback_limit {
                    eprintln!("Fallback limit reached");
                    false
                } else {
                    eprintln!("Using fallback counter: {}/{}", count, self.fallback_limit);
                    true
                }
            }
            Err(e) => {
                eprintln!("Unexpected error: {}", e);
                false
            }
        }
    }
}
```

## Monitoring Clock Errors

Track clock errors for alerting and debugging:

```rust
use std::sync::atomic::{AtomicU64, Ordering};

static CLOCK_ERROR_COUNT: AtomicU64 = AtomicU64::new(0);
static TOTAL_REQUESTS: AtomicU64 = AtomicU64::new(0);

fn check_with_monitoring(
    limiter: &FluxLimiter<String, SystemClock>,
    client_id: String
) -> bool {
    TOTAL_REQUESTS.fetch_add(1, Ordering::Relaxed);

    match limiter.check_request(client_id) {
        Ok(decision) => decision.allowed,
        Err(FluxLimiterError::ClockError(e)) => {
            CLOCK_ERROR_COUNT.fetch_add(1, Ordering::Relaxed);

            let error_count = CLOCK_ERROR_COUNT.load(Ordering::Relaxed);
            let total = TOTAL_REQUESTS.load(Ordering::Relaxed);
            let error_rate = error_count as f64 / total as f64;

            eprintln!("Clock error: {:?} (rate: {:.4}%)", e, error_rate * 100.0);

            // Implement your policy
            true // Fail-open
        }
        Err(e) => {
            eprintln!("Unexpected error: {}", e);
            false
        }
    }
}
```

## Circuit Breaker Pattern

Temporarily bypass rate limiting after consecutive failures:

```rust
use std::sync::atomic::{AtomicU64, Ordering};

struct CircuitBreakerLimiter {
    limiter: FluxLimiter<String, SystemClock>,
    consecutive_failures: AtomicU64,
    failure_threshold: u64,
    bypassed: AtomicU64,
}

impl CircuitBreakerLimiter {
    fn check_request(&self, client_id: String) -> bool {
        // Check if circuit is open
        if self.consecutive_failures.load(Ordering::Relaxed) >= self.failure_threshold {
            self.bypassed.fetch_add(1, Ordering::Relaxed);
            eprintln!("Circuit open - bypassing rate limiter");
            return true;
        }

        match self.limiter.check_request(client_id) {
            Ok(decision) => {
                // Reset failure counter on success
                self.consecutive_failures.store(0, Ordering::Relaxed);
                decision.allowed
            }
            Err(FluxLimiterError::ClockError(_)) => {
                let failures = self.consecutive_failures.fetch_add(1, Ordering::Relaxed) + 1;

                if failures >= self.failure_threshold {
                    eprintln!("Opening circuit after {} consecutive failures", failures);
                }

                true // Fail-open
            }
            Err(e) => {
                eprintln!("Unexpected error: {}", e);
                false
            }
        }
    }
}
```

## Cleanup Error Handling

The `cleanup_stale_clients` method can also return clock errors:

```rust
// Cleanup errors are typically not critical
match limiter.cleanup_stale_clients(one_hour_nanos) {
    Ok(()) => {
        // Cleanup succeeded
    }
    Err(FluxLimiterError::ClockError(_)) => {
        eprintln!("Clock error during cleanup - will retry later");
        // Cleanup failure is not critical - continue operation
    }
    Err(e) => {
        eprintln!("Unexpected cleanup error: {}", e);
    }
}
```

## Best Practices

1. **Validate Configuration Early**: Check configuration at startup, not runtime
2. **Choose an Error Policy**: Decide on fail-open, fail-closed, or fallback
3. **Monitor Errors**: Track error rates for alerting
4. **Log Contextually**: Include client ID and error context in logs
5. **Handle Gracefully**: Never panic - always return a decision
6. **Test Error Paths**: Use TestClock to simulate failures
7. **Document Policy**: Make your error handling policy explicit

## Next Steps

- [Advanced Usage]./advanced-usage.md - Memory management and optimization
- [Production Considerations]./production.md - Deploy with confidence
- [Testing Architecture]../architecture/testing.md - Test error handling