Quant.Explorer.RateLimiting.Behaviour behaviour (quant v0.1.0-alpha.1)

Behaviour for rate limiting backends.

This behaviour defines a flexible interface for rate limiting that can support various algorithms and backends (ETS, Redis, GenServer, etc.) and different provider-specific requirements.

Supported Rate Limiting Patterns

Simple Rate Limiting

  • Fixed window: N requests per time window
  • Sliding window: N requests in any sliding time window
  • Token bucket: Consume tokens at variable rates

Provider-Specific Patterns

  • Binance: Weight-based requests (different endpoints have different weights)
  • Yahoo Finance: IP-based limits with burst allowance
  • Alpha Vantage: API key-based with daily/monthly quotas
  • CoinGecko: Tiered limits based on API plan

Rate Limit Types

  • :requests_per_minute - Standard RPM limit
  • :requests_per_second - High-frequency limit
  • :requests_per_hour - Hourly quotas
  • :requests_per_day - Daily quotas
  • :weighted_requests - Weight-based limiting (Binance style)
  • :burst_allowance - Allow bursts with recovery

Summary

Callbacks

Checks if a request is allowed and records it if so.

Checks if a request would be allowed without consuming the limit.

Cleans up expired entries and performs maintenance.

Records a request consumption without checking.

Gets current limit status for a provider/endpoint combination.

Returns statistics about rate limiting (requests, violations, etc.).

Initializes the rate limiter backend.

Resets limits for a provider/endpoint (useful for testing or admin operations).

Sets up distributed coordination (for Redis, etc.).

Handles backend-specific configuration updates.

Functions

Helper function to create rate limit configurations.

Helper function to create a basic request info structure.

Types

endpoint()

@type endpoint() :: String.t() | atom()

limit_config()

@type limit_config() :: %{
  :endpoint => atom(),
  :type => limit_type(),
  :limit => pos_integer(),
  :weight => pos_integer(),
  :window_ms => pos_integer(),
  optional(:burst_size) => pos_integer(),
  optional(:recovery_rate) => pos_integer()
}

limit_type()

@type limit_type() ::
  :requests_per_minute
  | :requests_per_second
  | :requests_per_hour
  | :requests_per_day
  | :weighted_requests
  | :burst_allowance

provider()

@type provider() :: atom()

rate_limit_result()

@type rate_limit_result() :: :ok | {:error, :rate_limited} | {:error, term()}

remaining_info()

@type remaining_info() :: %{
  remaining: non_neg_integer(),
  reset_time: DateTime.t(),
  retry_after_ms: pos_integer()
}

request_info()

@type request_info() :: %{
  provider: provider(),
  endpoint: endpoint(),
  weight: weight(),
  user_id: String.t() | nil,
  ip_address: String.t() | nil
}

weight()

@type weight() :: pos_integer()

Callbacks

check_and_consume(request_info, limit_config, state)

@callback check_and_consume(request_info(), limit_config(), state :: term()) ::
  {rate_limit_result(), remaining_info(), new_state :: term()}

Checks if a request is allowed and records it if so.

This is the primary function that combines check and record operations for atomic rate limiting.

check_limit(request_info, limit_config, state)

@callback check_limit(request_info(), limit_config(), state :: term()) ::
  {rate_limit_result(), remaining_info(), state :: term()}

Checks if a request would be allowed without consuming the limit.

Useful for pre-flight checks or API exploration.

cleanup(state)

@callback cleanup(state :: term()) :: {:ok, new_state :: term()}

Cleans up expired entries and performs maintenance.

consume_limit(request_info, limit_config, state)

@callback consume_limit(request_info(), limit_config(), state :: term()) ::
  {remaining_info(), new_state :: term()}

Records a request consumption without checking.

Useful for tracking requests made outside the normal flow.

get_limit_status(provider, endpoint, state)

@callback get_limit_status(provider(), endpoint(), state :: term()) ::
  {remaining_info(), state :: term()}

Gets current limit status for a provider/endpoint combination.

get_stats(arg1, state)

@callback get_stats(provider() | :all, state :: term()) :: {map(), state :: term()}

Returns statistics about rate limiting (requests, violations, etc.).

init(opts)

@callback init(opts :: keyword()) :: {:ok, term()} | {:error, term()}

Initializes the rate limiter backend.

Returns {:ok, state} on success, {:error, reason} on failure.

reset_limits(provider, arg2, state)

@callback reset_limits(provider(), endpoint() | :all, state :: term()) ::
  {:ok, new_state :: term()}

Resets limits for a provider/endpoint (useful for testing or admin operations).

setup_distributed(nodes, state)

(optional)
@callback setup_distributed(nodes :: [node()], state :: term()) ::
  {:ok, new_state :: term()} | {:error, term()}

Sets up distributed coordination (for Redis, etc.).

update_config(new_config, state)

(optional)
@callback update_config(new_config :: keyword(), state :: term()) ::
  {:ok, new_state :: term()} | {:error, term()}

Handles backend-specific configuration updates.

Functions

limit_config(type, limit, opts \\ [])

@spec limit_config(limit_type(), pos_integer(), keyword()) :: %{
  :type => limit_type(),
  :limit => pos_integer(),
  :weight => pos_integer(),
  :window_ms => pos_integer(),
  optional(:burst_size) => pos_integer(),
  optional(:recovery_rate) => pos_integer()
}

Helper function to create rate limit configurations.

request_info(provider, endpoint, opts \\ [])

@spec request_info(provider(), endpoint(), keyword()) :: request_info()

Helper function to create a basic request info structure.