lib/maxwell/middleware/header_case.ex

defmodule Maxwell.Middleware.HeaderCase do
  @moduledoc """
  Forces all request headers to be of a certain case.

  ## Examples

      # Lower
      iex> conn = %Maxwell.Conn{req_headers: %{"content-type" => "application/json}}
      ...> Maxwell.Middleware.HeaderCase.request(conn, :lower)
      %Maxwell.Conn{req_headers: %{"content-type" => "application/json}}

      # Upper
      iex> conn = %Maxwell.Conn{req_headers: %{"content-type" => "application/json}}
      ...> Maxwell.Middleware.HeaderCase.request(conn, :upper)
      %Maxwell.Conn{req_headers: %{"CONTENT-TYPE" => "application/json}}

      # Title
      iex> conn = %Maxwell.Conn{req_headers: %{"content-type" => "application/json}}
      ...> Maxwell.Middleware.HeaderCase.request(conn, :title)
      %Maxwell.Conn{req_headers: %{"Content-Type" => "application/json}}
  """
  alias Maxwell.Conn

  def init(casing) when casing in [:lower, :upper, :title] do
    casing
  end

  def init(casing) do
    raise ArgumentError,
          "HeaderCase middleware expects a casing style of :lower, :upper, or :title - got: #{
            casing
          }"
  end

  def request(%Conn{req_headers: headers} = conn, :lower) do
    new_headers =
      headers
      |> Enum.map(fn {k, v} -> {String.downcase(k), v} end)
      |> Enum.into(%{})

    %{conn | req_headers: new_headers}
  end

  def request(%Conn{req_headers: headers} = conn, :upper) do
    new_headers =
      headers
      |> Enum.map(fn {k, v} -> {String.upcase(k), v} end)
      |> Enum.into(%{})

    %{conn | req_headers: new_headers}
  end

  def request(%Conn{req_headers: headers} = conn, :title) do
    new_headers =
      headers
      |> Enum.map(fn {k, v} ->
        tk =
          k
          |> String.downcase()
          |> String.split(~r/[-_]/, include_captures: true, trim: true)
          |> Enum.map(&String.capitalize/1)
          |> Enum.join()

        {tk, v}
      end)
      |> Enum.into(%{})

    %{conn | req_headers: new_headers}
  end
end