lib/arclight/plug_json_body.ex

defmodule Arclight.PlugJsonBody do
  @moduledoc """
  Plug to extract json in POST body into :json_body assign.
  """
  use Plug.Builder

  plug(:extract_json_body)

  @spec extract_json_body(Plug.Conn.t(), keyword) :: Plug.Conn.t()
  def extract_json_body(conn, opts) do
    case get_body(conn, opts) do
      {:body_params, ex_map} ->
          Plug.Conn.assign(conn, :json_body, ex_map)
      {:ok, body, conn} ->
        case Jason.decode(body) do
          {:ok, ex_map} ->
            Plug.Conn.assign(conn, :json_body, ex_map)

          _ ->
            fin(conn, "JSON required")
        end

      _ ->
        fin(conn, "POST with complete JSON expected")
    end
  end

  defp get_body(conn, _opts) do
    # Sorry for shitty defensive code, but this bit of Plugs is a bit ad hoc,
    # so I just write the same thing several times to make sure I won't crash
    # out like  I did a couple of times.
    if Map.get(conn, :body_params) do
      fetch_if_unfetched_and_return_body_params(conn.body_params, conn)
    else
      Plug.Conn.read_body(conn)
    end
  end

  defp fetch_if_unfetched_and_return_body_params(%Plug.Conn.Unfetched{} = _, conn) do
    Plug.Conn.read_body(conn)
  end
  defp fetch_if_unfetched_and_return_body_params(body_params, _conn) do
    {:body_params, body_params}
  end

  defp fin(c, msg) do
    c = send_resp(c, 403, msg)
    halt(c)
  end
end