lib/mix/tasks/query.ex

defmodule Mix.Tasks.Absinthe.Gen.Query do
  @shortdoc "Generates an absinthe query schema and inserts the record in the base schema.ex"

  @moduledoc """
  Generates an Absinthe Query

  ### Options

  #{NimbleOptions.docs(AbsintheGenerator.Query.definitions())}

  ### Specifying Queries
  The following format can be used to specify queries

  ```bash
  query_name:return_type:arg_name:arg_type:arg_name:arg_type:ResolverModule.resolver_function
  ```

  you can also specify middleware before or after the resolver

  ### Example

  ```bash
  mix absinthe.gen.query
    summoners:list_of(return_type):arg_a:string:arg_b:non_null(:integer):Resolvers.Summoner.list_all
    summoner:return_type:id:id:Resolvers.Summoner.find
    summoner:return_type:middleware:IDToIntegerMiddlewareid:id:middleware:AuthMiddleware:Resolvers.Summoner.find:middleware:ChangesetErrorFormatter
    --app-name MyApp
    --query-name students
    --moduledoc "this is the test"
  ```
  """

  use Mix.Task

  alias Mix.AbsintheGeneratorUtils
  alias Mix.AbsintheGeneratorUtils.SchemaFieldParser

  @query_regex ~r/^([a-z]+|list_of\([^\(\)]+\))+[A-Za-z\.]+(:middleware:[A-Za-z]+)?+$/

  def run(args) do
    AbsintheGeneratorUtils.ensure_not_in_umbrella!("absinthe.gen.query")

    {args, extra_args} = AbsintheGeneratorUtils.parse_path_opts(args, [
      path: :string,
      app_name: :string,
      moduledoc: :string,
      query_name: :string
    ])

    parsed_query_functions = extra_args
      |> validate_query_string
      |> SchemaFieldParser.parse_fields

    args
      |> Map.new
      |> Map.put(:queries, parsed_query_functions)
      |> serialize_to_query_struct
      |> IO.inspect
      |> AbsintheGenerator.Query.run
      |> AbsintheGeneratorUtils.write_template(path_from_args(args))
  end

  defp path_from_args(args) do
    Keyword.get(
      args,
      :path,
      "./lib/#{Macro.underscore(args[:app_name])}_web/queries/#{Macro.underscore(args[:query_name])}.ex"
    )
  end

  defp validate_query_string(query_parts) do
    if query_parts === [] or Enum.all?(query_parts, &Regex.match?(@query_regex, &1)) do
      query_parts
    else
      Mix.raise("""
      \n
      Query format isn't setup properly and must match the following regex

        #{inspect @query_regex}

      Example:

        summoners:list_of(return_type):arg_a:string:arg_b:non_null(:integer):Resolvers.Summoner.list_all
        summoner:return_type:id:id:Resolvers.Summoner.find
        summoner:return_type:middleware:IDToIntegerMiddlewareid:id:middleware:AuthMiddleware:Resolvers.Summoner.find:middleware:ChangesetErrorFormatter
      """)
    end
  end

  defp serialize_to_query_struct(params) do
    %AbsintheGenerator.Query{
      app_name: params[:app_name],
      moduledoc: params[:moduledoc],
      query_name: params[:query_name],
      queries: params[:queries]
    }
  end
end