# `HTTPower.Codec`
[🔗](https://github.com/mdepolli/httpower/blob/v0.22.0/lib/httpower/codec.ex#L1)

Request encoding for HTTPower.

This module handles encoding of request bodies and query parameters before
they are passed to the adapter layer.

## Body Encoding

Supports three mutually exclusive body options:

- `json: data` — Encodes `data` as JSON, sets `Content-Type: application/json`
  and `Accept: application/json` headers (unless already present).
- `form: data` — Encodes `data` as a URL-encoded form string, sets
  `Content-Type: application/x-www-form-urlencoded` (unless already present).
- `body: data` — Passes `data` through as-is. No encoding, no headers added.

Only one body option may be used per request. Combining `json:`, `form:`,
or `body:` in the same opts list returns an error.

## Query Parameters

- `params: data` — Encodes `data` as query parameters and appends to the
  request URL. Merges with any existing query string. Uses `URI.encode_query/1`
  (flat key-value only). Can be combined with any body option.

## Examples

    # JSON encoding
    iex> request = HTTPower.Request.new(:post, URI.parse("https://api.example.com/users"))
    iex> {:ok, encoded, opts} = HTTPower.Codec.encode_request(request, json: %{name: "Alice"})
    iex> encoded.body
    ~s({"name":"Alice"})
    iex> encoded.headers["Content-Type"]
    "application/json"
    iex> opts
    []

    # Form encoding
    iex> request = HTTPower.Request.new(:post, URI.parse("https://api.example.com/login"))
    iex> {:ok, encoded, opts} = HTTPower.Codec.encode_request(request, form: [user: "alice", pass: "secret"])
    iex> encoded.headers["Content-Type"]
    "application/x-www-form-urlencoded"
    iex> opts
    []

    # Existing Content-Type is preserved
    iex> request = HTTPower.Request.new(:post, URI.parse("https://api.example.com/users"), nil, %{"content-type" => "application/vnd.api+json"})
    iex> {:ok, encoded, _opts} = HTTPower.Codec.encode_request(request, json: %{name: "Alice"})
    iex> encoded.headers["content-type"]
    "application/vnd.api+json"
    iex> Map.has_key?(encoded.headers, "Content-Type")
    false

# `decode_response`

```elixir
@spec decode_response(
  HTTPower.Response.t(),
  keyword()
) :: HTTPower.Response.t()
```

Decodes the response body based on the Content-Type header.

Returns the updated response struct (not a tuple). Decoding is skipped when:
- `raw: true` is present in opts
- The body is not a binary (already decoded, e.g. a dedup cache hit)
- The body is nil or an empty string
- The Content-Type is not a JSON media type

Invalid JSON is left as the raw binary (no error is raised).

# `encode_request`

```elixir
@spec encode_request(
  HTTPower.Request.t(),
  keyword()
) :: {:ok, HTTPower.Request.t(), keyword()} | {:error, HTTPower.Error.t()}
```

Encodes the request body based on the encoding option present in `opts`.

Returns `{:ok, updated_request, updated_opts}` on success, or
`{:error, %HTTPower.Error{}}` on failure.

The encoding option (`:json` or `:form`) is removed from the returned opts.

# `json_content_type?`

```elixir
@spec json_content_type?(String.t() | nil) :: boolean()
```

Returns `true` if the given content type string is a JSON media type.

Recognises `application/json` (with optional parameters) and any media type
with a `+json` structured-syntax suffix, such as `application/vnd.api+json`.
Returns `false` for `nil`.

---

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