# `PhoenixGenApi.RateLimiter`
[🔗](https://github.com/ohhi-vn/phoenix_gen_api/blob/main/lib/phoenix_gen_api/rate_limiter/rate_limiter.ex#L1)

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

# `api_identifier`

```elixir
@type api_identifier() :: {String.t() | atom(), String.t()}
```

# `check_result`

```elixir
@type check_result() :: :ok | {:error, :rate_limited, rate_limit_details()}
```

# `rate_limit_details`

```elixir
@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()
}
```

# `rate_limit_key`

```elixir
@type rate_limit_key() :: :user_id | :device_id | :ip_address | String.t()
```

# `add_global_limit`

```elixir
@spec add_global_limit(map()) :: :ok
```

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
    })

# `attach_telemetry`

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)

# `check_rate_limit`

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

# `check_rate_limit`

```elixir
@spec check_rate_limit(String.t(), :global | api_identifier(), rate_limit_key()) ::
  :ok | {:error, :rate_limited, rate_limit_details()}
```

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)

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `clear`

```elixir
@spec clear() :: :ok
```

Clears all rate limit data from ETS tables.

Useful for testing or resetting rate limit counters.

# `detach_telemetry`

Detaches a telemetry handler by ID.

# `get_configured_limits`

```elixir
@spec get_configured_limits() :: %{global: list(), api: list()}
```

Gets all configured rate limits.

# `get_global_limits`

```elixir
@spec get_global_limits() :: [map()]
```

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}]

# `get_rate_limit_status`

```elixir
@spec get_rate_limit_status(String.t(), :global | api_identifier(), rate_limit_key()) ::
  map()
```

Gets current rate limit status for a key.

## Returns

  A map with current usage information for all applicable rate limits.

# `remove_global_limit`

```elixir
@spec remove_global_limit(atom() | String.t()) :: :ok
```

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)

# `reset_rate_limit`

```elixir
@spec reset_rate_limit(String.t(), :global | api_identifier(), rate_limit_key()) ::
  :ok
```

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)

# `set_global_limits`

```elixir
@spec set_global_limits([map()]) :: :ok
```

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}
    ])

# `start_link`

Starts the RateLimiter GenServer.

# `update_config`

```elixir
@spec update_config(map()) :: :ok
```

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}
      ]
    })

---

*Consult [api-reference.md](api-reference.md) for complete listing*
