Packages
phoenix_gen_api
2.10.0
2.22.0
2.21.1
2.21.0
2.20.1
2.20.0
2.19.0
2.18.1
2.18.0
2.17.1
2.17.0
2.16.0
2.15.0
2.14.0
2.13.0
2.12.0
2.11.0
2.10.0
2.9.1
2.9.0
2.8.0
2.7.0
2.6.1
2.6.0
2.5.0
2.4.0
2.3.0
2.2.0
2.1.1
2.1.0
2.0.1
2.0.0
1.2.0
1.1.3
1.1.2
1.1.1
1.1.0
1.0.1
1.0.0
0.2.1
0.2.0
0.1.1
0.1.0
0.0.16
0.0.15
0.0.14
0.0.13
0.0.12
0.0.11
0.0.10
0.0.9
0.0.8
0.0.7
0.0.6
0.0.5
0.0.4
0.0.3
0.0.2
0.0.1
A library for fast develop APIs in Elixir cluster, using Phoenix Channels for transport data, auto pull api configs from service nodes.
Current section
Files
Jump to
Current section
Files
lib/phoenix_gen_api/rate_limiter/rate_limiter.ex
defmodule PhoenixGenApi.RateLimiter do
@moduledoc """
Provides rate limiting functionality for API requests.
This module implements a sliding window rate limiter using ETS for high-performance
tracking. It supports both global rate limiting (across all APIs) and per-API
rate limiting with configurable limits.
## Architecture
The rate limiter uses a sliding window algorithm with ETS tables for storage:
- **Global Table**: Tracks request counts per key across all APIs
- **API Table**: Tracks request counts per key per API
## Rate Limiting Strategies
### Global Rate Limiting
Applies a single rate limit across all API requests for a given key.
Useful for preventing overall system abuse.
### Per-API Rate Limiting
Applies specific rate limits to individual API endpoints.
Useful for protecting expensive or sensitive operations.
## Configuration
Configure rate limits in your `config.exs`:
config :phoenix_gen_api, :rate_limiter,
enabled: true,
global_limits: [
# Default: 2000 requests per minute per user
%{key: :user_id, max_requests: 2000, window_ms: 60_000},
# Device-level: 10000 requests per minute per device
%{key: :device_id, max_requests: 10000, window_ms: 60_000}
],
api_limits: [
# Expensive operation: 10 requests per minute per user
%{
service: "data_service",
request_type: "export_data",
key: :user_id,
max_requests: 10,
window_ms: 60_000
},
# Public endpoint: 100 requests per minute per IP
%{
service: "public_service",
request_type: "search",
key: :ip_address,
max_requests: 100,
window_ms: 60_000
}
]
## Usage
### Basic Usage
# Check rate limit before executing a request
case RateLimiter.check_rate_limit(request) do
:ok ->
# Execute the request
Executor.execute!(request)
{:error, :rate_limited, details} ->
# Return rate limit error to client
Response.error_response(request.request_id, "Rate limit exceeded")
end
### Manual Rate Limiting
# Check global rate limit
RateLimiter.check_rate_limit("user_123", :global, :user_id)
# Check API-specific rate limit
RateLimiter.check_rate_limit("user_123", {"my_service", "my_api"}, :user_id)
## Rate Limit Keys
The rate limiter supports various key types:
- `:user_id` - Rate limit by user
- `:device_id` - Rate limit by device
- `:ip_address` - Rate limit by IP address
- Custom keys - Any string value
## Sliding Window Algorithm
The rate limiter uses a sliding window algorithm that:
1. Tracks individual request timestamps
2. Removes expired entries outside the window
3. Counts remaining entries to determine current usage
4. Provides accurate rate limiting without fixed window boundaries
## Performance
- ETS tables provide O(1) average-case lookups
- Cleanup runs periodically to remove expired entries
- Memory usage is bounded by max_requests × number of keys
- Read/write concurrency is enabled for high-throughput scenarios
## Fault Tolerance
- Rate limiter failures do not block request execution (fail-open by default)
- ETS tables are automatically cleaned up on process termination
- Configuration changes are applied without restart
"""
use GenServer, restart: :permanent
require Logger
@default_cleanup_interval 60_000
@doc """
Attaches a telemetry handler to rate limiter events.
## Events
- `[:phoenix_gen_api, :rate_limiter, :check]` - Emitted on every rate limit check
- `[:phoenix_gen_api, :rate_limiter, :exceeded]` - Emitted when a rate limit is exceeded
- `[:phoenix_gen_api, :rate_limiter, :reset]` - Emitted when rate limits are reset
- `[:phoenix_gen_api, :rate_limiter, :cleanup]` - Emitted during periodic cleanup
## Examples
# Attach a handler
:telemetry.attach(
"my-rate-limiter-handler",
[:phoenix_gen_api, :rate_limiter, :check],
fn event, measurements, metadata, config ->
...
end,
%{}
)
# Or use the helper function
PhoenixGenApi.RateLimiter.attach_telemetry("my-handler", &my_handler/4)
"""
def attach_telemetry(handler_id, function, config \\ %{})
when is_binary(handler_id) and is_function(function, 4) do
:telemetry.attach(handler_id, [:phoenix_gen_api, :rate_limiter, :check], function, config)
:telemetry.attach("#{handler_id}-exceeded", [:phoenix_gen_api, :rate_limiter, :exceeded], function, config)
:telemetry.attach("#{handler_id}-reset", [:phoenix_gen_api, :rate_limiter, :reset], function, config)
:telemetry.attach("#{handler_id}-cleanup", [:phoenix_gen_api, :rate_limiter, :cleanup], function, config)
:ok
end
@doc """
Detaches a telemetry handler by ID.
"""
def detach_telemetry(handler_id) when is_binary(handler_id) do
:telemetry.detach("#{handler_id}-check")
:telemetry.detach("#{handler_id}-exceeded")
:telemetry.detach("#{handler_id}-reset")
:telemetry.detach("#{handler_id}-cleanup")
:ok
end
@type rate_limit_key :: :user_id | :device_id | :ip_address | String.t()
@type api_identifier :: {String.t() | atom(), String.t()}
@type check_result :: :ok | {:error, :rate_limited, rate_limit_details()}
@type rate_limit_details :: %{
key: String.t(),
max_requests: non_neg_integer(),
current_requests: non_neg_integer(),
window_ms: non_neg_integer(),
retry_after_ms: non_neg_integer(),
scope: :global | api_identifier()
}
### Public API
@doc """
Starts the RateLimiter GenServer.
"""
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc """
Checks if a request is within rate limits.
This function checks both global and per-API rate limits configured for the
request. If any limit is exceeded, it returns an error with details.
## Parameters
- `request` - The `Request` struct to check
## Returns
- `:ok` - Request is within all rate limits
- `{:error, :rate_limited, details}` - Request exceeds a rate limit
## Examples
request = %Request{
user_id: "user_123",
device_id: "device_456",
service: "my_service",
request_type: "my_api"
}
case RateLimiter.check_rate_limit(request) do
:ok ->
# Proceed with request execution
{:error, :rate_limited, details} ->
# Return rate limit error
...
end
"""
def check_rate_limit(request) do
if enabled?() do
start_time = System.monotonic_time(:microsecond)
result = GenServer.call(__MODULE__, {:check_rate_limit, request})
duration = System.monotonic_time(:microsecond) - start_time
:telemetry.execute(
[:phoenix_gen_api, :rate_limiter, :check],
%{duration_us: duration},
%{
request_id: request.request_id,
user_id: request.user_id,
service: request.service,
request_type: request.request_type,
result: result
}
)
if match?({:error, :rate_limited, _}, result) do
{:error, :rate_limited, details} = result
:telemetry.execute(
[:phoenix_gen_api, :rate_limiter, :exceeded],
%{retry_after_ms: details.retry_after_ms},
%{
key: details.key,
scope: details.scope,
max_requests: details.max_requests,
current_requests: details.current_requests,
request_id: request.request_id,
user_id: request.user_id
}
)
end
result
else
:ok
end
rescue
e ->
if fail_open?() do
Logger.error("PhoenixGenApi.RateLimiter, check_rate_limit failed, allowing request: #{Exception.message(e)}")
:ok
else
{:error, :rate_limiter_error, %{message: Exception.message(e)}}
end
end
@doc """
Checks rate limit for a specific key and scope.
## Parameters
- `key_value` - The value to rate limit against (e.g., user ID)
- `scope` - Either `:global` or `{service, request_type}` tuple
- `rate_limit_key` - The type of key (`:user_id`, `:device_id`, etc.)
## Returns
- `:ok` - Within rate limit
- `{:error, :rate_limited, details}` - Exceeded rate limit
## Examples
# Check global rate limit for a user
RateLimiter.check_rate_limit("user_123", :global, :user_id)
# Check API-specific rate limit
RateLimiter.check_rate_limit("user_123", {"service", "api"}, :user_id)
"""
@spec check_rate_limit(String.t(), :global | api_identifier(), rate_limit_key()) ::
:ok | {:error, :rate_limited, rate_limit_details()}
def check_rate_limit(key_value, scope, rate_limit_key) when is_binary(key_value) do
if enabled?() do
GenServer.call(__MODULE__, {:check_rate_limit_direct, key_value, scope, rate_limit_key})
else
:ok
end
end
@doc """
Resets rate limit counters for a specific key.
## Parameters
- `key_value` - The key value to reset (e.g., user ID)
- `scope` - Either `:global` or `{service, request_type}` tuple
- `rate_limit_key` - The type of key
## Returns
- `:ok` - Counters were reset
## Examples
# Reset all rate limits for a user
RateLimiter.reset_rate_limit("user_123", :global, :user_id)
"""
@spec reset_rate_limit(String.t(), :global | api_identifier(), rate_limit_key()) :: :ok
def reset_rate_limit(key_value, scope, rate_limit_key) when is_binary(key_value) do
result = GenServer.call(__MODULE__, {:reset_rate_limit, key_value, scope, rate_limit_key})
:telemetry.execute(
[:phoenix_gen_api, :rate_limiter, :reset],
%{},
%{
key: key_value,
scope: scope,
rate_limit_key: rate_limit_key
}
)
result
end
@doc """
Gets current rate limit status for a key.
## Returns
A map with current usage information for all applicable rate limits.
"""
@spec get_rate_limit_status(String.t(), :global | api_identifier(), rate_limit_key()) :: map()
def get_rate_limit_status(key_value, scope, rate_limit_key) when is_binary(key_value) do
GenServer.call(__MODULE__, {:get_rate_limit_status, key_value, scope, rate_limit_key})
end
@doc """
Gets all configured rate limits.
"""
@spec get_configured_limits() :: %{global: list(), api: list()}
def get_configured_limits() do
GenServer.call(__MODULE__, :get_configured_limits)
end
@doc """
Gets the current global rate limits (may differ from config.exs if changed at runtime).
## Returns
A list of global rate limit maps.
## Examples
PhoenixGenApi.RateLimiter.get_global_limits()
# => [%{key: :user_id, max_requests: 2000, window_ms: 60_000}]
"""
@spec get_global_limits() :: [map()]
def get_global_limits() do
GenServer.call(__MODULE__, :get_global_limits)
end
@doc """
Sets (replaces) all global rate limits at runtime.
## Parameters
- `limits` - A list of global rate limit maps, each with:
- `:key` - The rate limit key (`:user_id`, `:device_id`, `:ip_address`, or custom string)
- `:max_requests` - Maximum requests allowed in the window
- `:window_ms` - Window duration in milliseconds
## Returns
- `:ok` - Limits were updated
## Examples
PhoenixGenApi.RateLimiter.set_global_limits([
%{key: :user_id, max_requests: 2000, window_ms: 60_000},
%{key: :device_id, max_requests: 10000, window_ms: 60_000}
])
"""
@spec set_global_limits([map()]) :: :ok
def set_global_limits(limits) when is_list(limits) do
GenServer.call(__MODULE__, {:set_global_limits, limits})
end
@doc """
Adds a single global rate limit at runtime.
If a limit with the same `:key` already exists, it will be replaced.
## Parameters
- `limit` - A map with `:key`, `:max_requests`, and `:window_ms`
## Returns
- `:ok` - Limit was added
## Examples
PhoenixGenApi.RateLimiter.add_global_limit(%{
key: :ip_address,
max_requests: 100,
window_ms: 60_000
})
"""
@spec add_global_limit(map()) :: :ok
def add_global_limit(limit) when is_map(limit) do
GenServer.call(__MODULE__, {:add_global_limit, limit})
end
@doc """
Removes a global rate limit by key at runtime.
## Parameters
- `key` - The rate limit key to remove (`:user_id`, `:device_id`, etc.)
## Returns
- `:ok` - Limit was removed (or didn't exist)
## Examples
PhoenixGenApi.RateLimiter.remove_global_limit(:ip_address)
"""
@spec remove_global_limit(atom() | String.t()) :: :ok
def remove_global_limit(key) do
GenServer.call(__MODULE__, {:remove_global_limit, key})
end
@doc """
Updates rate limit configuration at runtime.
## Parameters
- `config` - A map with `:global_limits` and/or `:api_limits` keys
## Returns
- `:ok` - Configuration was updated
## Examples
RateLimiter.update_config(%{
global_limits: [
%{key: :user_id, max_requests: 2000, window_ms: 60_000}
]
})
"""
@spec update_config(map()) :: :ok
def update_config(config) when is_map(config) do
GenServer.call(__MODULE__, {:update_config, config})
end
@doc """
Clears all rate limit data from ETS tables.
Useful for testing or resetting rate limit counters.
"""
@spec clear() :: :ok
def clear() do
GenServer.call(__MODULE__, :clear)
end
### Callbacks
@impl true
def init(_opts) do
# Create ETS tables for rate limiting
:ets.new(:rate_limiter_global, [
:set,
:named_table,
read_concurrency: true,
write_concurrency: true
])
:ets.new(:rate_limiter_api, [
:set,
:named_table,
read_concurrency: true,
write_concurrency: true
])
state = %{
global_limits: load_global_limits(),
api_limits: load_api_limits(),
cleanup_interval: cleanup_interval()
}
Logger.info(
"PhoenixGenApi.RateLimiter, initialized with #{length(state.global_limits)} global limits and #{length(state.api_limits)} API limits"
)
schedule_cleanup()
{:ok, state}
end
@impl true
def handle_call({:check_rate_limit, request}, _from, state) do
result = check_request_limits(request, state)
{:reply, result, state}
end
def handle_call({:check_rate_limit_direct, key_value, scope, rate_limit_key}, _from, state) do
result = check_direct_limit(key_value, scope, rate_limit_key, state)
{:reply, result, state}
end
def handle_call({:reset_rate_limit, key_value, scope, rate_limit_key}, _from, state) do
reset_limit(key_value, scope, rate_limit_key)
{:reply, :ok, state}
end
def handle_call({:get_rate_limit_status, key_value, scope, rate_limit_key}, _from, state) do
status = get_limit_status(key_value, scope, rate_limit_key, state)
{:reply, status, state}
end
def handle_call(:get_configured_limits, _from, state) do
{:reply, %{global: state.global_limits, api: state.api_limits}, state}
end
def handle_call(:get_global_limits, _from, state) do
{:reply, state.global_limits, state}
end
def handle_call({:set_global_limits, limits}, _from, state) do
Logger.info("PhoenixGenApi.RateLimiter, global limits replaced: #{inspect(limits)}")
{:reply, :ok, %{state | global_limits: limits}}
end
def handle_call({:add_global_limit, limit}, _from, state) do
key = Map.get(limit, :key)
new_limits =
state.global_limits
|> Enum.reject(fn l -> Map.get(l, :key) == key end)
|> Enum.concat([limit])
Logger.info("PhoenixGenApi.RateLimiter, global limit added/updated: #{inspect(limit)}")
{:reply, :ok, %{state | global_limits: new_limits}}
end
def handle_call({:remove_global_limit, key}, _from, state) do
new_limits = Enum.reject(state.global_limits, fn l -> Map.get(l, :key) == key end)
Logger.info("PhoenixGenApi.RateLimiter, global limit removed for key: #{inspect(key)}")
{:reply, :ok, %{state | global_limits: new_limits}}
end
def handle_call(:clear, _from, state) do
:ets.delete_all_objects(:rate_limiter_global)
:ets.delete_all_objects(:rate_limiter_api)
{:reply, :ok, state}
end
def handle_call({:update_config, config}, _from, state) do
new_state =
state
|> maybe_update_global_limits(config)
|> maybe_update_api_limits(config)
Logger.info("PhoenixGenApi.RateLimiter, configuration updated")
{:reply, :ok, new_state}
end
@impl true
def handle_info(:cleanup, state) do
start_time = System.monotonic_time(:microsecond)
cleaned_count = perform_cleanup()
duration = System.monotonic_time(:microsecond) - start_time
:telemetry.execute(
[:phoenix_gen_api, :rate_limiter, :cleanup],
%{duration_us: duration, cleaned_entries: cleaned_count},
%{
global_limits_count: length(state.global_limits),
api_limits_count: length(state.api_limits)
}
)
schedule_cleanup()
{:noreply, state}
end
@impl true
def handle_info(_msg, state) do
{:noreply, state}
end
@impl true
def terminate(_reason, _state) do
try do
:ets.delete(:rate_limiter_global)
:ets.delete(:rate_limiter_api)
catch
:error, :badarg -> :ok
end
:ok
end
### Private Functions
defp check_request_limits(request, state) do
# Check global limits
case check_global_limits(request, state.global_limits) do
{:error, _reason, _details} = error -> error
:ok -> check_api_limits(request, state.api_limits)
end
end
defp check_global_limits(request, global_limits) do
Enum.reduce_while(global_limits, :ok, fn limit, _acc ->
key_value = get_key_value(request, limit.key)
if is_binary(key_value) and byte_size(key_value) > 0 do
case check_and_record(:rate_limiter_global, key_value, limit) do
:ok -> {:cont, :ok}
{:error, :rate_limited, details} -> {:halt, {:error, :rate_limited, details}}
end
else
{:cont, :ok}
end
end)
end
defp check_api_limits(request, api_limits) do
Enum.reduce_while(api_limits, :ok, fn limit, _acc ->
if limit.service == request.service and limit.request_type == request.request_type do
key_value = get_key_value(request, limit.key)
if is_binary(key_value) and byte_size(key_value) > 0 do
scope = {limit.service, limit.request_type}
case check_and_record(:rate_limiter_api, build_api_key(key_value, scope), limit) do
:ok -> {:cont, :ok}
{:error, :rate_limited, details} -> {:halt, {:error, :rate_limited, details}}
end
else
{:cont, :ok}
end
else
{:cont, :ok}
end
end)
end
defp check_direct_limit(key_value, scope, rate_limit_key, state) do
limits =
case scope do
:global -> state.global_limits
{service, request_type} ->
Enum.filter(state.api_limits, fn limit ->
limit.service == service and limit.request_type == request_type and limit.key == rate_limit_key
end)
end
if limits == [] do
:ok
else
Enum.reduce_while(limits, :ok, fn limit, _acc ->
ets_key =
case scope do
:global -> key_value
_ -> build_api_key(key_value, scope)
end
case check_and_record(ets_table_for_scope(scope), ets_key, limit) do
:ok -> {:cont, :ok}
{:error, :rate_limited, details} -> {:halt, {:error, :rate_limited, details}}
end
end)
end
end
defp check_and_record(table, key, limit) do
now = System.monotonic_time(:millisecond)
window_start = now - limit.window_ms
# Get existing timestamps
existing_timestamps =
case :ets.lookup(table, key) do
[{^key, timestamps}] -> timestamps
[] -> []
end
# Partition timestamps into valid and expired in a single pass
{valid_timestamps, _expired} =
Enum.split_with(existing_timestamps, fn ts -> ts > window_start end)
current_count = length(valid_timestamps)
if current_count >= limit.max_requests do
# Calculate retry-after using the oldest valid timestamp
oldest_valid = Enum.min(valid_timestamps)
retry_after_ms = oldest_valid + limit.window_ms - now
details = %{
key: key,
max_requests: limit.max_requests,
current_requests: current_count,
window_ms: limit.window_ms,
retry_after_ms: max(retry_after_ms, 0),
scope: get_scope_from_table(table, key)
}
Logger.warning(
"PhoenixGenApi.RateLimiter, rate limit exceeded for key: #{inspect(key)}, " <>
"current: #{current_count}/#{limit.max_requests}, retry_after: #{retry_after_ms}ms"
)
{:error, :rate_limited, details}
else
# Add current timestamp and update ETS in a single operation
:ets.insert(table, {key, [now | valid_timestamps]})
:ok
end
end
defp reset_limit(key_value, scope, _rate_limit_key) do
table = ets_table_for_scope(scope)
ets_key =
case scope do
:global -> key_value
_ -> build_api_key(key_value, scope)
end
:ets.delete(table, ets_key)
Logger.info("PhoenixGenApi.RateLimiter, reset rate limit for key: #{inspect(key_value)}, scope: #{inspect(scope)}")
end
defp get_limit_status(key_value, scope, _rate_limit_key, state) do
table = ets_table_for_scope(scope)
ets_key =
case scope do
:global -> key_value
_ -> build_api_key(key_value, scope)
end
limits =
case scope do
:global -> state.global_limits
{service, request_type} ->
Enum.filter(state.api_limits, fn limit ->
limit.service == service and limit.request_type == request_type
end)
end
now = System.monotonic_time(:millisecond)
existing_timestamps =
case :ets.lookup(table, ets_key) do
[{^ets_key, timestamps}] -> timestamps
[] -> []
end
# Pre-compute valid count once if all limits share the same window
# Otherwise compute per-limit
Enum.map(limits, fn limit ->
window_start = now - limit.window_ms
valid_count = count_valid_timestamps(existing_timestamps, window_start)
%{
key: ets_key,
max_requests: limit.max_requests,
current_requests: valid_count,
window_ms: limit.window_ms,
remaining: max(limit.max_requests - valid_count, 0),
scope: scope
}
end)
end
# Optimized timestamp counting - stops early if we only need the count
defp count_valid_timestamps(timestamps, window_start) do
Enum.count(timestamps, fn ts -> ts > window_start end)
end
defp get_key_value(request, key) do
case key do
:user_id -> request.user_id
:device_id -> request.device_id
:ip_address -> Map.get(request, :ip_address)
custom_key when is_binary(custom_key) -> Map.get(request.args, custom_key)
_ -> nil
end
end
defp build_api_key(key_value, {service, request_type}) do
"#{service}:#{request_type}:#{key_value}"
end
defp ets_table_for_scope(:global), do: :rate_limiter_global
defp ets_table_for_scope({_service, _request_type}), do: :rate_limiter_api
defp get_scope_from_table(:rate_limiter_global, _key), do: :global
defp get_scope_from_table(:rate_limiter_api, key) do
case String.split(key, ":", parts: 3) do
[service, request_type, _key_value] -> {service, request_type}
_ -> :global
end
end
defp perform_cleanup() do
now = System.monotonic_time(:millisecond)
# Clean global table
global_cleaned = cleanup_table(:rate_limiter_global, now)
# Clean API table
api_cleaned = cleanup_table(:rate_limiter_api, now)
Logger.debug("PhoenixGenApi.RateLimiter, cleanup completed, removed #{global_cleaned + api_cleaned} entries")
global_cleaned + api_cleaned
end
defp cleanup_table(table, now) do
max_window = get_max_window_for_table(table)
cutoff = now - max_window
:ets.foldl(
fn {key, timestamps}, acc ->
# Use split_with for single-pass partitioning
{valid_timestamps, expired} = Enum.split_with(timestamps, fn ts -> ts > cutoff end)
if expired == [] do
# No expired entries, skip update
acc
else
if valid_timestamps == [] do
:ets.delete(table, key)
else
:ets.insert(table, {key, valid_timestamps})
end
acc + length(expired)
end
end,
0,
table
)
end
defp get_max_window_for_table(:rate_limiter_global) do
case Application.get_env(:phoenix_gen_api, :rate_limiter, [])[:global_limits] do
nil -> @default_cleanup_interval
limits -> Enum.map(limits, & &1.window_ms) |> Enum.max(fn -> @default_cleanup_interval end)
end
end
defp get_max_window_for_table(:rate_limiter_api) do
case Application.get_env(:phoenix_gen_api, :rate_limiter, [])[:api_limits] do
nil -> @default_cleanup_interval
limits -> Enum.map(limits, & &1.window_ms) |> Enum.max(fn -> @default_cleanup_interval end)
end
end
defp schedule_cleanup() do
Process.send_after(self(), :cleanup, @default_cleanup_interval)
end
defp load_global_limits() do
case Application.get_env(:phoenix_gen_api, :rate_limiter, [])[:global_limits] do
nil -> []
limits when is_list(limits) -> limits
_ -> []
end
end
defp load_api_limits() do
case Application.get_env(:phoenix_gen_api, :rate_limiter, [])[:api_limits] do
nil -> []
limits when is_list(limits) -> limits
_ -> []
end
end
defp enabled?() do
Application.get_env(:phoenix_gen_api, :rate_limiter, [])[:enabled] != false
end
defp fail_open?() do
Application.get_env(:phoenix_gen_api, :rate_limiter, [])[:fail_open] != false
end
defp cleanup_interval() do
Application.get_env(:phoenix_gen_api, :rate_limiter, [])[:cleanup_interval] || @default_cleanup_interval
end
defp maybe_update_global_limits(state, %{global_limits: limits}) when is_list(limits) do
%{state | global_limits: limits}
end
defp maybe_update_global_limits(state, _), do: state
defp maybe_update_api_limits(state, %{api_limits: limits}) when is_list(limits) do
%{state | api_limits: limits}
end
defp maybe_update_api_limits(state, _), do: state
end