lib/time_zone_info/downloader/mint.ex

defmodule TimeZoneInfo.Downloader.Mint do
  @moduledoc """
  An implementation of the `TimeZoneInfo.Downloader` behaviour using `Mint`.
  """

  @behaviour TimeZoneInfo.Downloader

  alias Mint.HTTP

  @impl true
  def download(uri, opts) do
    method = "GET"
    headers = opts[:headers] || []
    http = http!()

    with {:ok, conn} <- http.connect(scheme(uri), uri.host, uri.port),
         {:ok, conn, request_ref} <- http.request(conn, method, path(uri), headers, ""),
         {:ok, conn, body} <- receive_response(conn, request_ref),
         {:ok, _conn} <- http.close(conn) do
      {:ok, body}
    else
      error -> {:error, error}
    end
  end

  defp path(%URI{path: path, query: nil}), do: path

  defp path(%URI{path: path, query: query}), do: "#{path}?#{query}"

  defp receive_response(conn, request_ref, status \\ nil, body \\ []) do
    {conn, status, {body, complete}} = receive_message(conn, request_ref, status, body)

    case complete do
      :incomplete ->
        receive_response(conn, request_ref, status, body)

      :complete ->
        {:ok, conn, {status, Enum.join(body)}}
    end
  end

  defp receive_message(conn, request_ref, status, body) do
    http = HTTP

    receive do
      message ->
        {:ok, conn, responses} = http.stream(conn, message)

        {status, {body, complete}} =
          Enum.reduce(
            responses,
            {status, {body, :incomplete}},
            fn response, {status, {body, complete}} ->
              case response do
                {:status, ^request_ref, status} ->
                  {status, {body, complete}}

                {:data, ^request_ref, data} ->
                  {status, {body ++ [data], :incomplete}}

                {:done, ^request_ref} ->
                  {status, {body, :complete}}

                {_incomplete, ^request_ref, _data} ->
                  {status, {body, :incomplete}}
              end
            end
          )

        {conn, status, {body, complete}}
    end
  end

  defp scheme(%{scheme: "http"}), do: :http

  defp scheme(%{scheme: "https"}), do: :https

  defp http! do
    case Code.ensure_loaded?(HTTP) do
      true ->
        HTTP

      false ->
        raise """
        Can't find dependency for Mint. Please add :mint to your deps.

        defp deps do
          [
            {:mint, "~> 1.0"},
            {:castore, "~> 0.1"},
            ...
          ]
        end
        """
    end
  end
end