///|
/// Fixed-window rate-limit middleware over the process-local limiter: account for each
/// HTTP request under `key` and answer `429 Too Many Requests` once the key is over
/// quota for the current window, otherwise delegating to the wrapped app. The limiter
/// is captured once per assembly, so its windows are shared across every request this
/// layer serves — but only within this process; use `redis_period_limit` to share them
/// across replicas. Non-HTTP scopes pass through untouched.
pub fn period_limit(
  limiter : @limit.PeriodLimit,
  clock : @clock.Clock,
  key~ : String = "global",
  refusal~ : Refusal = too_many,
) -> Middleware {
  inner => {
    (scope, receive, send) => {
      match scope {
        Http(_) =>
          match limiter.take(key, clock.now()) {
            PeriodOverQuota =>
              for event in refusal.events() {
                send(event)
              }
            _ => inner(scope, receive, send)
          }
        _ => inner(scope, receive, send)
      }
    }
  }
}

///|
/// Fixed-window rate-limit middleware over a shared redis: `period_limit` with the
/// window in redis, so replicas behind one redis spend one quota between them. A redis
/// that cannot be reached admits the request — the limiter is a guard on the
/// service, not a dependency that should be able to close it.
pub fn redis_period_limit(
  limiter : @limit.RedisPeriodLimit,
  key~ : String = "global",
  refusal~ : Refusal = too_many,
) -> Middleware {
  inner => {
    (scope, receive, send) => {
      match scope {
        Http(_) => {
          let outcome = limiter.take(key) catch {
            RedisError(_) => PeriodAllowed
          }
          match outcome {
            PeriodOverQuota =>
              for event in refusal.events() {
                send(event)
              }
            _ => inner(scope, receive, send)
          }
        }
        _ => inner(scope, receive, send)
      }
    }
  }
}