Bourse.RateLimiter.Shaping (bourse v0.1.0)

Copy Markdown View Source

Shapes endpoint rate-limit descriptors into Bourse.RateLimiter checks and updates rate-limit state from response headers.

Extracted from Bourse.HTTP so transport stays focused on request execution while bucket-axis normalization, cost shaping, and header→ETS updates live with the RateLimiter family. Behavior-preserving delegation only.

Summary

Types

Full rate key including bucket axis

Credential slice of a rate key: API key string or :public

One RateLimiter.check_rates/1 triple

Base rate key without bucket axis

Functions

Builds RateLimiter check triples from an endpoint rate-limit descriptor.

Checks rate limit if enabled — blocks until capacity is available, returns :ok.

Parses rate limit headers from a response and updates the ETS state store.

Builds the base rate-limiter key from an exchange: {exchange_id, api_key | :public}.

Types

axis_key()

@type axis_key() :: {String.t(), credential_key(), String.t()}

Full rate key including bucket axis

credential_key()

@type credential_key() :: String.t() | :public

Credential slice of a rate key: API key string or :public

rate_check()

@type rate_check() ::
  {axis_key(), %{requests: non_neg_integer(), period: pos_integer()}, number()}

One RateLimiter.check_rates/1 triple

rate_key()

@type rate_key() :: {String.t(), credential_key()}

Base rate key without bucket axis

Functions

build_rate_limit_checks(rate_key, exchange, endpoint_rate_limit)

@spec build_rate_limit_checks(rate_key(), Bourse.Exchange.t(), term()) :: [
  rate_check()
]

Builds RateLimiter check triples from an endpoint rate-limit descriptor.

Accepts a numeric weight, a map with :cost/:axes/:rate_limit_ms, a list of those maps, or falls back to weight 1 on the default "request" axis.

maybe_rate_limit(rate_key, exchange, endpoint_rate_limit)

@spec maybe_rate_limit(rate_key(), Bourse.Exchange.t(), term()) :: :ok

Checks rate limit if enabled — blocks until capacity is available, returns :ok.

rate_limit_ms is "milliseconds between requests", so max requests per period = period / rate_limit_ms.

maybe_update_state(exchange, resp_headers)

@spec maybe_update_state(Bourse.Exchange.t(), map()) :: :ok

Parses rate limit headers from a response and updates the ETS state store.

Returns :ok when the exchange doesn't send rate limit headers (OKX, Kraken, etc.) — the normal case for most exchanges, not an error.

rate_key(exchange)

@spec rate_key(Bourse.Exchange.t()) :: rate_key()

Builds the base rate-limiter key from an exchange: {exchange_id, api_key | :public}.