lib/absinthe/plug/graphiql.ex

defmodule Absinthe.Plug.GraphiQL do
  @moduledoc """
  Enables GraphiQL

  # Usage

  ```elixir
  if Absinthe.Plug.GraphiQL.serve? do
    plug Absinthe.Plug.GraphiQL
  end
  ```
  """

  require EEx
  @graphiql_version "0.7.8"
  EEx.function_from_file :defp, :graphiql_html, Path.join(__DIR__, "graphiql.html.eex"),
    [:graphiql_version, :query_string, :variables_string, :result_string]

  @graphql_toolbox_version "1.0.1"
  EEx.function_from_file :defp, :graphql_toolbox_html, Path.join(__DIR__, "graphql_toolbox.html.eex"),
    [:graphql_toolbox_version, :query_string, :variables_string]

  @behaviour Plug

  import Plug.Conn
  import Absinthe.Plug, only: [prepare: 3, setup_pipeline: 3, load_body_and_params: 1]

  @type opts :: [
    schema: atom,
    adapter: atom,
    path: binary,
    context: map,
    json_codec: atom | {atom, Keyword.t},
    interface: atom
  ]

  @doc """
  Sets up and validates the Absinthe schema
  """
  @spec init(opts :: opts) :: map
  def init(opts) do
    opts
    |> Absinthe.Plug.init
    |> Map.put(:interface, Keyword.get(opts, :interface, :advanced))
  end

  def call(conn, config) do
    case html?(conn) do
      true -> do_call(conn, config)
      _ -> Absinthe.Plug.call(conn, config)
    end
  end

  defp html?(conn) do
    Plug.Conn.get_req_header(conn, "accept")
    |> List.first
    |> case do
      string when is_binary(string) -> String.contains?(string, "text/html")
      _ -> false
    end
  end

  defp do_call(conn, %{json_codec: _, interface: interface} = config) do
    {conn, body} = load_body_and_params(conn)

    with {:ok, input, opts} <- prepare(conn, body, config),
    pipeline <- setup_pipeline(conn, config, opts),
    {:ok, result, _} <- Absinthe.Pipeline.run(input, pipeline) do
      {:ok, result, opts[:variables], input}
    end
    |> case do
      {:ok, result, variables, query} ->
        query = query |> js_escape

        var_string = variables
        |> Poison.encode!(pretty: true)
        |> js_escape

        result = result
        |> Poison.encode!(pretty: true)
        |> js_escape

        html = case interface do
          :advanced -> graphql_toolbox_html(@graphql_toolbox_version, query, var_string)
          :simple -> graphiql_html(@graphiql_version, query, var_string, result)
        end

        conn
        |> put_resp_content_type("text/html")
        |> send_resp(200, html)

      {:input_error, msg} ->
        conn
        |> send_resp(400, msg)

      {:error, {:http_method, text}, _} ->
        conn
        |> send_resp(405, text)

      {:error, error, _} when is_binary(error) ->
        conn
        |> send_resp(500, error)

    end
  end

  defp js_escape(string) do
    string
    |> String.replace(~r/\n/, "\\n")
    |> String.replace(~r/'/, "\\'")
  end
end