nautechsystems/nautilus_trader · error · TransportError

Connection rate limiter requires at least one connection rat

Error message

Connection rate limiter requires at least one connection rate key

What it means

The mirror-side validation of resolve_connection_rate_limit: a connection rate limiter without at least one key has nothing to look up in the limiter, which is a configuration mistake. The builder rejects it at construction with this InvalidInput TransportError rather than creating a rate limiter that would never be consulted.

Source

Thrown at crates/network/src/websocket/client.rs:2856

                cancellation_token,
            },
        )
        .await
    }

    fn resolve_connection_rate_limit(
        rate_limiter: Option<Arc<RateLimiter<Ustr, MonotonicClock>>>,
        keys: Arc<[Ustr]>,
    ) -> Result<Option<ConnectionRateLimit>, TransportError> {
        if rate_limiter.is_none() && !keys.is_empty() {
            return Err(TransportError::Io(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "Connection rate keys require a connection rate limiter",
            )));
        }

        if rate_limiter.is_some() && keys.is_empty() {
            return Err(TransportError::Io(std::io::Error::new(
                std::io::ErrorKind::InvalidInput,
                "Connection rate limiter requires at least one connection rate key",
            )));
        }

        Ok(rate_limiter.map(|limiter| ConnectionRateLimit { limiter, keys }))
    }

    fn resolve_rate_limiter(
        default_quota: Option<Quota>,
        keyed_quotas: Vec<(String, Quota)>,
        rate_limiter: Option<Arc<RateLimiter<Ustr, MonotonicClock>>>,
    ) -> Result<Arc<RateLimiter<Ustr, MonotonicClock>>, TransportError> {
        if let Some(rate_limiter) = rate_limiter {
            if default_quota.is_some() || !keyed_quotas.is_empty() {
                return Err(TransportError::Io(std::io::Error::new(
                    std::io::ErrorKind::InvalidInput,
                    "Cannot combine a shared rate limiter with quota configuration",

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Provide at least one connection rate key when configuring a rate limiter
  2. If rate limiting is not needed, omit the limiter entirely instead of passing one with no keys
  3. Make the config layer require key(s) whenever the limiter flag/section is present

Example fix

// before
builder.connection_rate_limiter(limiter) // keys empty
// after
builder
    .connection_rate_limiter(limiter)
    .connection_rate_keys(Arc::from([ustr("default")]))
Defensive patterns

Strategy: validation

Validate before calling

if rate_limiter.is_some() && connection_rate_keys.is_empty() {
    return Err(anyhow!("rate limiter set without connection rate keys"));
}

Try / catch

match WebSocketClient::new(...) {
    Err(e) if e.to_string().contains("requires at least one connection rate key") => {
        // supply keys or drop the limiter, then rebuild
    }
    r => r?,
}

Prevention

When it happens

Trigger: Building WebSocketClient with Some(rate_limiter) but an empty connection_rate_keys slice.

Common situations: Constructing the limiter unconditionally but only filling keys conditionally (e.g. keys loaded from config that was empty); a refactor that separated limiter creation from key wiring; default limiter in a shared builder helper with per-environment key lists.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/ede5be20639701e2. Report an issue: GitHub.