defmodule Mix.Tasks.Absinthe.Gen.Schema do use Mix.Task alias Mix.AbsintheGeneratorUtils @shortdoc "Generates an absinthe schema" @moduledoc """ Generates an Absinthe Schema ### Options #{NimbleOptions.docs(AbsintheGenerator.Schema.definitions())} ### Specifying Middleware To specify middleware we can utilize the following syntax ```bash pre_middleware:mutation:AuthMiddleware post_middleware:all:ChangesetErrorFormatter ``` Middleware can be set for `mutation`, `query`, `subscription` or `all` and can also be set to either run pre or post resolution using `pre_middleware` or `post_middleware` ### Example ```bash mix absinthe.gen.schema pre_middleware:mutation:MyMiddlwareModule post_middleware:all:MyAllMiddleware --app-name MyApp --query test --query user --mutation user --mutation session --type MyApp --moduledoc "this is the test" --data-source "EctoSchemas.Cats" ``` """ @middleware_regex ~r/(pre_middleware|post_middleware):(mutation|query|subscription|all):[a-zA-Z_]+/ def run(args) do AbsintheGeneratorUtils.ensure_not_in_umbrella!("absinthe.gen.schema") {args, extra_args} = AbsintheGeneratorUtils.parse_path_opts(args, [ path: :string, app_name: :string, moduledoc: :string, type: :keep, query: :keep, mutation: :keep, subscription: :keep, data_source: :keep ]) parsed_middleware = extra_args |> validate_middleware_string |> parse_middleware path = Keyword.get(args, :path, "./lib/#{Macro.underscore(args[:app_name])}_web/schema.ex") args |> AbsintheGeneratorUtils.collect_arguments([:query, :mutation, :subscription, :type, :data_source]) |> Map.merge(parsed_middleware) |> serialize_to_schema_struct |> AbsintheGenerator.Schema.run |> AbsintheGeneratorUtils.write_template(path) end defp validate_middleware_string(middleware_args) do middleware_string = Enum.join(middleware_args, " ") if middleware_string === "" or Regex.match?(@middleware_regex, middleware_string) do middleware_args else Mix.raise(""" \n Middleware format doesn't match what's expected, please make sure it matches the following Regex: #{inspect @middleware_regex} Example: pre_middleware:mutation:MyMiddlewareModule pre_middleware:query:MyMiddlewareModule pre_middleware:all:MyMiddlewareModule """) end end defp parse_middleware(extra_args) do middleware_acc = %{ pre_middleware: [], post_middleware: [] } Enum.reduce(extra_args, middleware_acc, fn (arg_string, acc) -> [ middleware_type, middleware_query_type, middleware_module ] = String.split(arg_string, ":") middleware_type = String.to_atom(middleware_type) middleware = [%{ type: middleware_query_type, module: middleware_module }] Map.update(acc, middleware_type, middleware, &(&1 ++ middleware)) end) end defp serialize_to_schema_struct(params) do data_sources = params |> Map.get(:data_source, []) |> Enum.map(&struct!(AbsintheGenerator.Schema.DataSource, %{source: &1, query: nil})) pre_middleware = params |> Map.get(:pre_middleware, []) |> serialize_middleware post_middleware = params |> Map.get(:post_middleware, []) |> serialize_middleware %AbsintheGenerator.Schema{ app_name: params[:app_name], moduledoc: params[:moduledoc], queries: params[:query] || [], mutations: params[:mutation] || [], subscriptions: params[:subscription] || [], types: params[:type] || [], data_sources: data_sources, post_middleware: post_middleware, pre_middleware: pre_middleware } end defp serialize_middleware(middleware_params) do middleware_params |> Enum.group_by(&(&1.module), &(&1.type)) |> Enum.map(fn {module, types} -> %{types: types, module: module} end) |> Enum.map(&struct!(AbsintheGenerator.Schema.Middleware, &1)) end end