Skip to main content

lib/treasury_prime/http_client.ex

defmodule TreasuryPrime.HTTPClient do
  @moduledoc """
  Behaviour for the HTTP transport used by `TreasuryPrime.Client`.

  TreasuryPrime ships with a zero-dependency default adapter
  (`TreasuryPrime.HTTPClient.Httpc`) built on Erlang's `:httpc`, so the
  library works out of the box without pulling in Req, Finch, Hackney, or
  any other HTTP stack. If your application already depends on one of those
  (very likely, since most production apps do), you can plug it in instead by
  implementing this behaviour and passing `http_client: YourAdapter` to
  `TreasuryPrime.Client.new/1`.

  ## Writing your own adapter

      defmodule MyApp.ReqAdapter do
        @behaviour TreasuryPrime.HTTPClient

        @impl true
        def request(method, url, headers, body, opts) do
          case Req.request(method: method, url: url, headers: headers, body: body,
                            receive_timeout: Keyword.get(opts, :receive_timeout, 30_000),
                            retry: false) do
            {:ok, resp} -> {:ok, %{status: resp.status, headers: resp.headers, body: resp.body}}
            {:error, reason} -> {:error, reason}
          end
        end
      end

      TreasuryPrime.Client.new(api_key_id: "...", api_key_value: "...", http_client: MyApp.ReqAdapter)
  """

  @type method :: :get | :post | :patch | :put | :delete
  @type headers :: [{String.t(), String.t()}]
  @type response :: %{status: pos_integer(), headers: headers(), body: binary()}

  @doc """
  Performs a single HTTP request and returns the raw response.

  Implementations must NOT raise; transport failures (timeouts, DNS errors,
  TLS errors, connection refused, etc.) must be returned as
  `{:error, reason}`. The caller (`TreasuryPrime.HTTP`) is responsible for
  JSON encoding/decoding, retries, and turning non-2xx responses into
  `TreasuryPrime.Error` structs.
  """
  @callback request(method(), url :: String.t(), headers(), body :: iodata(), opts :: keyword()) ::
              {:ok, response()} | {:error, term()}
end