# `Noizu.Discord.Request`
[🔗](https://github.com/noizu-labs-sdks/elixir-discord/blob/v0.1.0/lib/noizu/discord/request.ex#L1)

Request engine behind every resource module (REST only — no gateway).

Builds the Req call (`Authorization: Bot <token>`, Discord `User-Agent`), decodes
JSON, applies the bounded retry policy, and normalizes failures to
`%Noizu.Discord.Error{}`.

## Return shapes

  * `{:ok, decoded_body}` — 2xx; `decoded_body` is `nil` for empty (204) responses.
  * `{:ok, decoded_body, meta}` — when the call is made with `meta: true`.
  * `{:error, %Noizu.Discord.Error{}}`.

`meta` is `%{status: integer, attempts: pos_integer, rate_limit: rate_limit}` where
`rate_limit` is built from the `X-RateLimit-*` headers (see `rate_limit_meta/1`).

## Retry policy

  * **429** — always retried (Discord rejects rate-limited requests without
    applying them, so this is safe for POST/PATCH too). The delay is the JSON
    `retry_after` (seconds, float), else `X-RateLimit-Reset-After`, else
    `Retry-After`, else exponential-ish backoff; clamped to `:max_delay_ms`.
  * **5xx** — retried only for idempotent methods (`GET`, `HEAD`, `PUT`, `DELETE`).
  * At most `:max_attempts` (default 3) total attempts; then the last error is
    returned (`tag: :rate_limited` for a final 429).

## Common per-call options

  * `:meta` — `true` to receive `{:ok, body, meta}`.
  * `:reason` — sets `X-Audit-Log-Reason` (URL-encoded) for moderation actions.

# `meta`

```elixir
@type meta() :: %{
  status: pos_integer(),
  attempts: pos_integer(),
  rate_limit: rate_limit()
}
```

# `method`

```elixir
@type method() :: :get | :post | :put | :patch | :delete | :head
```

# `rate_limit`

```elixir
@type rate_limit() :: %{
  bucket: String.t() | nil,
  limit: integer() | nil,
  remaining: integer() | nil,
  reset: float() | nil,
  reset_after: float() | nil,
  scope: String.t() | nil,
  global: boolean()
}
```

# `result`

```elixir
@type result() ::
  {:ok, term()} | {:ok, term(), meta()} | {:error, Noizu.Discord.Error.t()}
```

# `encode_segment`

```elixir
@spec encode_segment(term()) :: String.t()
```

Percent-encodes one path segment (ids, emoji, tokens).

# `perform`

```elixir
@spec perform(Noizu.Discord.Client.t(), method(), String.t(), keyword()) :: result()
```

Performs one API call.

Options: `:json` (body), `:params` (query; `nil` values dropped), `:headers`,
`:auth` (default `true`; `false` omits the bot token — webhooks/interactions),
`:meta`, `:reason`.

# `rate_limit_meta`

```elixir
@spec rate_limit_meta(map()) :: rate_limit()
```

Extracts rate-limit info from response headers (`X-RateLimit-Bucket`, `-Limit`,
`-Remaining`, `-Reset`, `-Reset-After`, `-Scope`, `-Global`). Missing values are `nil`.

# `split_opts`

```elixir
@spec split_opts(keyword(), [atom()]) :: {keyword(), keyword()}
```

Splits resource-call options into `{query_params, request_opts}`.

`query_keys` are moved to query params; `:meta`, `:reason`, `:headers` stay request
options. Unknown keys are ignored.

---

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