Skip to main content

lib/http/request.ex

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