lib/oasis/controller.ex

defmodule Oasis.Controller do
  @moduledoc ~S"""
  Base on `Plug.Builder`, this module can be `use`-d into a module in order to build a plug pipeline:

      defmodule MyApp.HelloController do
        use Oasis.Controller

        plug(Plug.Parsers,
          parsers: [:urlencoded],
          pass: ["*/*"]
        )

        def call(conn, opts) do
          conn = super(conn, opts)
          json(conn, %{"body_params" => conn.body_params})
        end
      end

  And provide some common functionality for easily use, this realization comes from
  [Phoenix.Controller](https://hexdocs.pm/phoenix/Phoenix.Controller.html).
  """
  import Plug.Conn

  defmacro __using__(_opts) do
    quote location: :keep do
      import Oasis.Controller

      use Plug.Builder
    end
  end

  @doc """
  Returns the router module as an atom, raises if unavailable.
  """
  @spec router_module(Plug.Conn.t) :: atom()
  def router_module(conn), do: conn.private.oasis_router

  @doc """
  Sends text response.

  ## Examples
  
      iex> text(conn, "hello")

      iex> text(conn, :implements_to_string)

  """
  @spec text(Plug.Conn.t(), String.Chars.t()) :: Plug.Conn.t()
  def text(conn, data) do
    send_resp(conn, conn.status || 200, "text/plain", to_string(data))
  end

  @doc """
  Sends html response.

  ## Examples

      iex> html(conn, "<html>...</html>")

  """
  @spec html(Plug.Conn.t(), iodata()) :: Plug.Conn.t()
  def html(conn, data) do
    send_resp(conn, conn.status || 200, "text/html", data)
  end

  @doc """
  Sends JSON response.

  It uses `Jason` to encode the input as an iodata data.
  
  ## Examples

      iex> json(conn, %{id: 1})

  """
  @spec json(Plug.Conn.t(), term) :: Plug.Conn.t()
  def json(conn, data) do
    response = Jason.encode_to_iodata!(data)
    send_resp(conn, conn.status || 200, "application/json", response)
  end

  defp send_resp(conn, default_status, default_content_type, body) do
    conn
    |> ensure_resp_content_type(default_content_type)
    |> send_resp(conn.status || default_status, body)
  end

  defp ensure_resp_content_type(%Plug.Conn{resp_headers: resp_headers} = conn, content_type) do
    if List.keyfind(resp_headers, "content-type", 0) do
      conn
    else
      content_type = content_type <> "; charset=utf-8"
      %Plug.Conn{conn | resp_headers: [{"content-type", content_type} | resp_headers]}
    end
  end
end