# `HTTP.Headers`
[🔗](https://github.com/gsmlg-dev/http_fetch/blob/v0.16.7/apps/http_core/lib/http/headers.ex#L1)

HTTP headers processing and manipulation utilities.

This module provides a comprehensive API for working with HTTP headers, including
case-insensitive lookups, header normalization, Content-Type parsing, and a
default User-Agent generator.

## Features

- **Case-insensitive operations**: All header name comparisons are case-insensitive
- **Header normalization**: Converts header names to Title-Case format
- **Multiple headers**: Support for multiple values for the same header name
- **Content-Type parsing**: Extract media type and parameters
- **Default User-Agent**: Automatically generated based on runtime environment

## Basic Usage

    # Create headers
    headers = HTTP.Headers.new([
      {"Content-Type", "application/json"},
      {"Authorization", "Bearer token"}
    ])

    # Get header value (case-insensitive)
    content_type = HTTP.Headers.get(headers, "content-type")
    # => "application/json"

    # Set/update header
    headers = HTTP.Headers.set(headers, "Accept", "application/json")

    # Set only if not present
    headers = HTTP.Headers.set_default(headers, "User-Agent", "CustomAgent/1.0")

    # Add multiple values for same header
    headers = HTTP.Headers.add(headers, "Accept", "text/html")
    values = HTTP.Headers.get_all(headers, "Accept")
    # => ["application/json", "text/html"]

## Content-Type Parsing

    {media_type, params} =
      HTTP.Headers.parse_content_type("application/json; charset=utf-8")
    # => {"application/json", %{"charset" => "utf-8"}}

## User-Agent

The library provides a default User-Agent string that includes:

- Operating system information
- System architecture
- OTP version
- BEAM version
- Elixir version
- Library version

Example User-Agent:

    "Mozilla/5.0 (macOS; x86_64-apple-darwin24.6.0) OTP/27 BEAM/15.1 Elixir/1.18.3 http_core/0.5.0"

Access the default User-Agent:

    user_agent = HTTP.Headers.user_agent()

# `header`

```elixir
@type header() :: {String.t(), String.t()}
```

# `headers_list`

```elixir
@type headers_list() :: [header()]
```

# `t`

```elixir
@type t() :: %HTTP.Headers{headers: headers_list()}
```

# `add`

```elixir
@spec add(t(), String.t(), String.t()) :: t()
```

Adds a new header value without removing existing headers with the same name.

## Examples
    iex> headers = HTTP.Headers.new([{"Accept", "text/html"}])
    iex> updated = HTTP.Headers.add(headers, "Accept", "application/json")
    iex> HTTP.Headers.get_all(updated, "Accept")
    ["text/html", "application/json"]
    
    iex> headers = HTTP.Headers.new()
    iex> updated = HTTP.Headers.add(headers, "Authorization", "Bearer token")
    iex> HTTP.Headers.get(updated, "Authorization")
    "Bearer token"
    
    iex> headers = HTTP.Headers.new([{"Content-Type", "text/plain"}])
    iex> updated = HTTP.Headers.add(headers, "Authorization", "Bearer token")
    iex> HTTP.Headers.get(updated, "Authorization")
    "Bearer token"
    iex> HTTP.Headers.get(updated, "Content-Type")
    "text/plain"

# `delete`

```elixir
@spec delete(t(), String.t()) :: t()
```

Removes a header by name (case-insensitive).

## Examples
    iex> headers = HTTP.Headers.new([{"Content-Type", "application/json"}, {"Authorization", "Bearer token"}])
    iex> updated = HTTP.Headers.delete(headers, "content-type")
    iex> HTTP.Headers.has?(updated, "content-type")
    false
    iex> HTTP.Headers.has?(updated, "Authorization")
    true

# `format`

```elixir
@spec format(t()) :: String.t()
```

Formats headers for display (useful for debugging).

## Examples
    iex> headers = HTTP.Headers.new([{"Content-Type", "application/json"}, {"Authorization", "Bearer token"}])
    iex> HTTP.Headers.format(headers)
    "Content-Type: application/json
Authorization: Bearer token"

# `from_map`

```elixir
@spec from_map(map()) :: t()
```

Converts a map of headers to a HTTP.Headers struct.

## Examples
    iex> headers = HTTP.Headers.from_map(%{"content-type" => "application/json", "authorization" => "Bearer token"})
    iex> {"Content-Type", "application/json"} in headers.headers
    true
    iex> {"Authorization", "Bearer token"} in headers.headers
    true

# `get`

```elixir
@spec get(t(), String.t()) :: String.t() | nil
```

Gets a header value by name (case-insensitive).

## Examples
    iex> headers = HTTP.Headers.new([{"Content-Type", "application/json"}])
    iex> HTTP.Headers.get(headers, "content-type")
    "application/json"
    
    iex> headers = HTTP.Headers.new([{"Authorization", "Bearer token"}])
    iex> HTTP.Headers.get(headers, "missing")
    nil

# `get_all`

```elixir
@spec get_all(t(), String.t()) :: [String.t()]
```

Gets all header values for a given header name (case-insensitive).

## Examples
    iex> headers = HTTP.Headers.new([{"Accept", "text/html"}, {"Accept", "application/json"}])
    iex> HTTP.Headers.get_all(headers, "accept")
    ["text/html", "application/json"]
    
    iex> headers = HTTP.Headers.new([{"Content-Type", "application/json"}, {"Authorization", "Bearer token"}])
    iex> HTTP.Headers.get_all(headers, "content-type")
    ["application/json"]
    
    iex> headers = HTTP.Headers.new([{"Content-Type", "application/json"}])
    iex> HTTP.Headers.get_all(headers, "missing")
    []

# `has?`

```elixir
@spec has?(t(), String.t()) :: boolean()
```

Checks if a header exists (case-insensitive).

## Examples
    iex> headers = HTTP.Headers.new([{"Content-Type", "application/json"}])
    iex> HTTP.Headers.has?(headers, "content-type")
    true
    
    iex> headers = HTTP.Headers.new([{"Content-Type", "application/json"}])
    iex> HTTP.Headers.has?(headers, "missing")
    false

# `merge`

```elixir
@spec merge(t(), t()) :: t()
```

Merges two HTTP.Headers structs, with the second taking precedence.

## Examples
    iex> headers1 = HTTP.Headers.new([{"Content-Type", "text/plain"}])
    iex> headers2 = HTTP.Headers.new([{"Content-Type", "application/json"}])
    iex> merged = HTTP.Headers.merge(headers1, headers2)
    iex> HTTP.Headers.get(merged, "Content-Type")
    "application/json"
    
    iex> headers1 = HTTP.Headers.new([{"A", "1"}])
    iex> headers2 = HTTP.Headers.new([{"B", "2"}])
    iex> merged = HTTP.Headers.merge(headers1, headers2)
    iex> HTTP.Headers.get(merged, "A")
    "1"
    iex> HTTP.Headers.get(merged, "B")
    "2"

# `new`

```elixir
@spec new(headers_list()) :: t()
```

Creates a new HTTP.Headers struct.

## Examples
    iex> HTTP.Headers.new([{"Content-Type", "application/json"}])
    %HTTP.Headers{headers: [{"Content-Type", "application/json"}]}
    
    iex> HTTP.Headers.new()
    %HTTP.Headers{headers: []}

# `normalize_name`

```elixir
@spec normalize_name(String.t()) :: String.t()
```

Normalizes header names to title case (e.g., "content-type" becomes "Content-Type").

## Examples
    iex> HTTP.Headers.normalize_name("content-type")
    "Content-Type"
    
    iex> HTTP.Headers.normalize_name("AUTHORIZATION")
    "Authorization"

# `parse`

```elixir
@spec parse(String.t()) :: header()
```

Parses a header string into a {name, value} tuple.

## Examples
    iex> HTTP.Headers.parse("Content-Type: application/json")
    {"Content-Type", "application/json"}
    
    iex> HTTP.Headers.parse("Authorization: Bearer token123")
    {"Authorization", "Bearer token123"}

# `parse_content_type`

```elixir
@spec parse_content_type(String.t()) :: {String.t(), map()}
```

Parses a Content-Type header to extract the media type and parameters.

## Examples
    iex> HTTP.Headers.parse_content_type("application/json; charset=utf-8")
    {"application/json", %{"charset" => "utf-8"}}
    
    iex> HTTP.Headers.parse_content_type("text/plain")
    {"text/plain", %{}}

# `set`

```elixir
@spec set(t(), String.t(), String.t()) :: t()
```

Sets a header value, replacing any existing header with the same name.

## Examples
    iex> headers = HTTP.Headers.new([{"Content-Type", "text/plain"}])
    iex> updated = HTTP.Headers.set(headers, "Content-Type", "application/json")
    iex> HTTP.Headers.get(updated, "Content-Type")
    "application/json"
    
    iex> headers = HTTP.Headers.new()
    iex> updated = HTTP.Headers.set(headers, "Authorization", "Bearer token")
    iex> HTTP.Headers.get(updated, "Authorization")
    "Bearer token"

# `set_default`

```elixir
@spec set_default(t(), String.t(), String.t()) :: t()
```

Sets a header only if it doesn't already exist (case-insensitive).

## Examples
    iex> headers = HTTP.Headers.new([{"Content-Type", "text/plain"}])
    iex> updated = HTTP.Headers.set_default(headers, "Content-Type", "application/json")
    iex> HTTP.Headers.get(updated, "Content-Type")
    "text/plain"
    
    iex> headers = HTTP.Headers.new([{"Accept", "text/html"}])
    iex> updated = HTTP.Headers.set_default(headers, "User-Agent", "CustomAgent/1.0")
    iex> HTTP.Headers.get(updated, "User-Agent")
    "CustomAgent/1.0"
    
    iex> headers = HTTP.Headers.new()
    iex> updated = HTTP.Headers.set_default(headers, "Authorization", "Bearer token")
    iex> HTTP.Headers.get(updated, "Authorization")
    "Bearer token"

# `to_list`

```elixir
@spec to_list(t()) :: headers_list()
```

Returns the underlying list of headers.

## Examples
    iex> headers = HTTP.Headers.new([{"Content-Type", "application/json"}])
    iex> HTTP.Headers.to_list(headers)
    [{"Content-Type", "application/json"}]

# `to_map`

```elixir
@spec to_map(t()) :: map()
```

Converts a HTTP.Headers struct to a map for easy lookup.

## Examples
    iex> headers = HTTP.Headers.new([{"Content-Type", "application/json"}, {"Authorization", "Bearer token"}])
    iex> HTTP.Headers.to_map(headers)
    %{"content-type" => "application/json", "authorization" => "Bearer token"}

# `user_agent`

```elixir
@spec user_agent(atom()) :: String.t()
```

Returns the default User-Agent string used by the library.

## Examples
    iex> user_agent = HTTP.Headers.user_agent()
    iex> user_agent =~ "Mozilla/5.0"
    true
    iex> user_agent =~ "http_core/"
    true

---

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