lib/tesla/adapter/finch.ex

if Code.ensure_loaded?(Finch) do
  defmodule Tesla.Adapter.Finch do
    @moduledoc """
    Adapter for [finch](https://github.com/sneako/finch).

    Remember to add `{:finch, "~> 0.14.0"}` to dependencies. Also, you need to
    recompile tesla after adding the `:finch` dependency:

    ```
    mix deps.clean tesla
    mix compile
    ```

    ## Examples

    In order to use Finch, you must start it and provide a `:name`. For example,
    in your supervision tree:

    ```elixir
    children = [
      {Finch, name: MyFinch}
    ]
    ```

    You must provide the same name to this adapter:

    ```
    # set globally in config/config.exs
    config :tesla, :adapter, {Tesla.Adapter.Finch, name: MyFinch}

    # set per module
    defmodule MyClient do
      use Tesla

      adapter Tesla.Adapter.Finch, name: MyFinch
    end
    ```

    ## Adapter specific options

      * `:name` - The `:name` provided to Finch (**required**).

    ## [Finch options](https://hexdocs.pm/finch/Finch.html#request/3)

      * `:pool_timeout` - This timeout is applied when a connection is checked
        out from the pool. Default value is `5_000`.

      * `:receive_timeout` - The maximum time to wait for a response before
        returning an error. Default value is `15_000`.

    """
    @behaviour Tesla.Adapter
    alias Tesla.Multipart

    @impl Tesla.Adapter
    def call(%Tesla.Env{} = env, opts) do
      opts = Tesla.Adapter.opts(env, opts)

      name = Keyword.fetch!(opts, :name)
      url = Tesla.build_url(env.url, env.query)
      req_opts = Keyword.take(opts, [:pool_timeout, :receive_timeout])

      case request(name, env.method, url, env.headers, env.body, req_opts) do
        {:ok, %Finch.Response{status: status, headers: headers, body: body}} ->
          {:ok, %Tesla.Env{env | status: status, headers: headers, body: body}}

        {:error, mint_error} ->
          {:error, Exception.message(mint_error)}
      end
    end

    defp request(name, method, url, headers, %Multipart{} = mp, opts) do
      headers = headers ++ Multipart.headers(mp)
      body = Multipart.body(mp) |> Enum.to_list()

      request(name, method, url, headers, body, opts)
    end

    defp request(_name, _method, _url, _headers, %Stream{}, _opts) do
      raise "Streaming is not supported by this adapter!"
    end

    defp request(name, method, url, headers, body, opts) do
      Finch.build(method, url, headers, body)
      |> Finch.request(name, opts)
    end
  end
end