defmodule HTTP.Request do
@moduledoc """
HTTP request configuration struct.
This module provides a structured way to build HTTP requests with proper
conversion to the internal HTTP/1.1 wire format. It handles method normalization,
header processing, body encoding, and FormData support.
## Supported Methods
- `:get` - GET requests (no body)
- `:head` - HEAD requests (no body)
- `:post` - POST requests (with body)
- `:put` - PUT requests (with body)
- `:delete` - DELETE requests (no body)
- `:patch` - PATCH requests (with body)
## Usage
# Basic request
request = %HTTP.Request{
method: :get,
url: URI.parse("https://api.example.com/data"),
headers: HTTP.Headers.new([{"Accept", "application/json"}])
}
# POST with JSON body
request = %HTTP.Request{
method: :post,
url: URI.parse("https://api.example.com/posts"),
headers: HTTP.Headers.new([{"Authorization", "Bearer token"}]),
content_type: "application/json",
body: JSON.encode!(%{title: "Hello"}),
transport_options: [timeout: 10_000]
}
# FormData upload
form = HTTP.FormData.new()
|> HTTP.FormData.append_field("name", "John")
|> HTTP.FormData.append_file("photo", "photo.jpg", file_stream)
request = %HTTP.Request{
method: :post,
url: URI.parse("https://api.example.com/upload"),
body: form
}
## Default Headers
The library automatically adds a `User-Agent` header if not provided,
containing information about the runtime environment (OS, architecture,
OTP version, Elixir version, and library version).
"""
defstruct method: :get,
url: nil,
headers: %HTTP.Headers{},
# Separate field for Content-Type header
content_type: nil,
body: nil,
# Socket transport options (e.g., timeout, connect_timeout, ssl, redirect)
transport_options: []
@type method :: :head | :get | :post | :put | :delete | :patch
@type url :: URI.t()
@type content_type :: String.t() | charlist() | nil
@type body_content :: String.t() | charlist() | HTTP.FormData.t() | nil
@type t :: %__MODULE__{
method: method,
url: url,
headers: HTTP.Headers.t(),
content_type: content_type,
body: body_content,
transport_options: Keyword.t()
}
@doc """
Converts an `HTTP.Request` struct into HTTP/1.1 wire iodata.
"""
@spec to_iodata(t()) :: iolist()
def to_iodata(%__MODULE__{} = req), do: HTTP.HTTP1.serialize_request(req)
end