lib/response.ex

defmodule ExCurl.Response do
  @moduledoc """
  A valid response returned by ExCurl.

  ## Metrics

  When the `metrics_returned` field is set to `true`, the following metrics will be included:

    * `total_time` - corresponds to the [CURLINFO_TOTAL_TIME_T option](https://curl.se/libcurl/c/CURLINFO_TOTAL_TIME_T.html).
    * `namelookup_time` - corresponds to the [CURLINFO_NAMELOOKUP_TIME_T option](https://curl.se/libcurl/c/CURLINFO_NAMELOOKUP_TIME_T.html).
    * `connect_time` - corresponds to the [CURLINFO_CONNECT_TIME_T option](https://curl.se/libcurl/c/CURLINFO_CONNECT_TIME_T.html).
    * `appconnect_time` - corresponds to the [CURLINFO_APPCONNECT_TIME_T option](https://curl.se/libcurl/c/CURLINFO_APPCONNECT_TIME_T.html).
    * `pretransfer_time` - corresponds to the [CURLINFO_PRETRANSFER_TIME_T option](https://curl.se/libcurl/c/CURLINFO_PRETRANSFER_TIME_T.html).
    * `starttransfer_time` - corresponds to the [CURLINFO_STARTTRANSFER_TIME_T option](https://curl.se/libcurl/c/CURLINFO_STARTTRANSFER_TIME_T.html).

  """
  defstruct body: "",
            headers: %{},
            status_code: 200,
            total_time: 0,
            namelookup_time: 0,
            connect_time: 0,
            appconnect_time: 0,
            pretransfer_time: 0,
            starttransfer_time: 0,
            metrics_returned: false

  @type t :: %__MODULE__{
          body: nil | String.t(),
          headers: map(),
          status_code: integer(),
          total_time: float(),
          namelookup_time: float(),
          connect_time: float(),
          appconnect_time: float(),
          pretransfer_time: float(),
          starttransfer_time: float(),
          metrics_returned: bool()
        }

  @doc false
  def from_keyword_list_response(list) do
    response = struct(__MODULE__, list)

    %__MODULE__{
      response
      | headers: header_string_to_map(response.headers)
    }
  end

  defp header_string_to_map(headers) do
    headers
    |> String.split("\r\n")
    |> Stream.filter(&String.contains?(&1, ":"))
    |> Stream.map(&String.split(&1, ": "))
    |> Stream.map(fn [head | rest] -> [head, Enum.join(rest, ": ")] end)
    |> Stream.map(fn [key, value] -> {key, value} end)
    |> Enum.into(%{})
  end
end