lib/cognac.ex

defmodule Cognac do
  @moduledoc """

  """
  alias Cognac.Query
  alias Cognac.OperationArgument

  @doc """
  Convert keyword list/tuple list to GraphQL query string

  ```elixir
  iex> query = [hero: [:name, friends: [:name]]]
  iex> Cognac.query(query) |> IO.puts
  query{hero{name friends{name}}}
  ```

  ## Options

  * `:output`
    * `:iodata` (default) - Outputs as IO data
    * `:binary` - Outputs query as a binary string
  * `pretty`
    * `false` (default) - Outputs minimal query
    * `true` - Outputs prettified query with indentation and linebreaks
  """
  @spec query(list() | keyword()) :: binary() | iodata()
  def query(query, options \\ []) do
    ["query", Query.build(query, options)]
    |> binary_or_iodata(options)
  end

  @doc """
  Convert keyword list/tuple list to GraphQL mutation query string

  ```elixir
  iex> mutation = [hero: [:name, friends: [:name]]]
  iex> Cognac.mutation(mutation) |> IO.puts
  mutation{updateHero(name:\"Steve\"){name}}
  ```

  ## Options

  * `:output`
    * `:iodata` (default) - Outputs as IO data
    * `:binary` - Outputs query as a binary string
  * `pretty`
    * `false` (default) - Outputs minimal query
    * `true` - Outputs prettified query with indentation and linebreaks
  """
  @spec mutation(list() | keyword()) :: binary() | iodata()
  def mutation(query, options \\ []) do
    ["mutation", Query.build(query, options)]
    |> binary_or_iodata(options)
  end

  @doc """
  Convert keyword list/tuple list to GraphQL subscription query string

  ```elixir
  iex> subscription = [heroUpdated: [:name]]
  iex> Cognac.subscription(subscription) |> IO.puts
  subscription{heroUpdated{name}}
  ```

  ## Options

  * `:output`
    * `:iodata` (default) - Outputs as IO data
    * `:binary` - Outputs query as a binary string
  * `pretty`
    * `false` (default) - Outputs minimal query
    * `true` - Outputs prettified query with indentation and linebreaks
  """

  @spec subscription(list() | keyword()) :: binary() | iodata()
  def subscription(query, options \\ []) do
    ["subscription", Query.build(query, options)]
    |> binary_or_iodata(options)
  end

  @spec query_operation(binary(), list() | keyword(), list() | keyword(), keyword()) ::
          binary() | iodata()
  def query_operation(name, arguments, query, options \\ []) do
    arguments = OperationArgument.build(arguments)
    ["query ", name, arguments, " ", Query.build(query, options)]
  end

  @spec mutation_operation(binary(), list() | keyword(), list() | keyword(), keyword()) ::
          binary() | iodata()
  def mutation_operation(name, arguments, query, options \\ []) do
    arguments = OperationArgument.build(arguments)
    ["mutation ", name, arguments, Query.build(query, options)]
  end

  @spec subscription_operation(binary(), list() | keyword(), list() | keyword(), keyword()) ::
          binary() | iodata()
  def subscription_operation(name, arguments, query, options \\ []) do
    arguments = OperationArgument.build(arguments)
    ["subscription ", name, arguments, Query.build(query, options)]
  end

  @doc """
  Marks value as variable, helping with correct annotation using ($) and all that jazz.
  """
  defmacro variable(name) when is_binary(name) do
    quote do: {:variable, unquote(name)}
  end

  defmacro variable(name) when is_atom(name) do
    quote do: {:variable, unquote(name)}
  end

  @doc """
  Marks type as non-null, appending with an exclamation mark (!).
  """
  defmacro non_null(type) do
    quote do: [to_string(unquote(type)), ?!]
  end

  defp binary_or_iodata(iodata, options) do
    case options[:output] do
      :iodata -> iodata
      :binary -> IO.iodata_to_binary(iodata)
      _ -> iodata
    end
  end
end