lib/fennel/pipeline.ex

defmodule Fennel.Pipeline do
  @moduledoc """
  For simplicity, we match phases from Absinthe.Pipeline.
  """

  # No need to execute everything during query validation
  # in the future, possibly disable more elements if they're not needed
  # like telemetry?

  alias Absinthe.Phase

  def prepare(options) do
    pipeline =
      Keyword.fetch!(options, :schema)
      |> Absinthe.Pipeline.for_document(options)
      |> Absinthe.Pipeline.upto(Phase.Document.CurrentOperation)
      |> Absinthe.Pipeline.reject(
        &(&1 in [
            Phase.Document.Validation.ProvidedAnOperation
          ])
      )
      |> (fn pipeline ->
            if Keyword.get(options, :add_typenames) do
              Absinthe.Pipeline.insert_after(
                pipeline,
                Phase.Parse,
                Fennel.Phase.Document.InsertTypeName
              )
            else
              pipeline
            end
          end).()

    pipeline ++ [Phase.Document.Result]
  end

  @doc """
  Arguments validation is mostly disabled here.
  """
  def validation(options) do
    [
      Phase.Document.Uses,
      # Validate Document Structure
      {Phase.Document.Validation.NoFragmentCycles, options},
      Phase.Document.Validation.LoneAnonymousOperation,
      # {Phase.Document.Validation.SelectedCurrentOperation, options},
      Phase.Document.Validation.KnownFragmentNames,
      Phase.Document.Validation.NoUndefinedVariables,
      Phase.Document.Validation.NoUnusedVariables,
      # Phase.Document.Validation.NoUnusedFragments,
      Phase.Document.Validation.UniqueFragmentNames,
      Phase.Document.Validation.UniqueOperationNames,
      Phase.Document.Validation.UniqueVariableNames,
      # Apply Input
      {Phase.Document.Context, options},
      {Phase.Document.Variables, options},
      # Phase.Document.Validation.ProvidedNonNullVariables,
      # Phase.Document.Arguments.Normalize,
      # Map to Schema
      {Phase.Schema, options},
      # Ensure Types
      Phase.Validation.KnownTypeNames,
      # Phase.Document.Arguments.VariableTypesMatch,
      # Process Arguments
      # Phase.Document.Arguments.CoerceEnums,
      # Phase.Document.Arguments.CoerceLists,
      # {Phase.Document.Arguments.Parse, options},
      Phase.Document.MissingVariables,
      Phase.Document.MissingLiterals,
      # Phase.Document.Arguments.FlagInvalid,
      # Validate Full Document
      Phase.Document.Validation.KnownDirectives,
      Phase.Document.Validation.RepeatableDirectives,
      Phase.Document.Validation.ScalarLeafs,
      # Phase.Document.Validation.VariablesAreInputTypes,
      # Phase.Document.Validation.ArgumentsOfCorrectType,
      Phase.Document.Validation.KnownArgumentNames,
      # Phase.Document.Validation.ProvidedNonNullArguments,
      Phase.Document.Validation.UniqueArgumentNames,
      Phase.Document.Validation.UniqueInputFieldNames,
      Phase.Document.Validation.FieldsOnCorrectType,
      Phase.Document.Validation.OnlyOneSubscription,
      Fennel.Phase.Document.Validation.DeprecatedFields,
      # Check Validation
      {Phase.Document.Validation.Result, options},
      # Format Result
      Phase.Document.Result
    ]
  end

  def runner(options) do
    [
      Phase.Document.Validation.ProvidedAnOperation,
      Phase.Document.Uses,
      # Validate Document Structure
      {Phase.Document.Validation.NoFragmentCycles, options},
      Phase.Document.Validation.LoneAnonymousOperation,
      {Phase.Document.Validation.SelectedCurrentOperation, options},
      Phase.Document.Validation.KnownFragmentNames,
      Phase.Document.Validation.NoUndefinedVariables,
      Phase.Document.Validation.NoUnusedVariables,
      Phase.Document.Validation.NoUnusedFragments,
      Phase.Document.Validation.UniqueFragmentNames,
      Phase.Document.Validation.UniqueOperationNames,
      Phase.Document.Validation.UniqueVariableNames,
      # Apply Input
      {Phase.Document.Context, options},
      {Phase.Document.Variables, options},
      Phase.Document.Validation.ProvidedNonNullVariables,
      Phase.Document.Arguments.Normalize,
      # Map to Schema
      {Phase.Schema, options},
      # Ensure Types
      Phase.Validation.KnownTypeNames,
      Phase.Document.Arguments.VariableTypesMatch,
      # Process Arguments
      Phase.Document.Arguments.CoerceEnums,
      Phase.Document.Arguments.CoerceLists,
      {Phase.Document.Arguments.Parse, options},
      Phase.Document.MissingVariables,
      Phase.Document.MissingLiterals,
      Phase.Document.Arguments.FlagInvalid,
      # Validate Full Document
      Phase.Document.Validation.KnownDirectives,
      Phase.Document.Validation.RepeatableDirectives,
      Phase.Document.Validation.ScalarLeafs,
      Phase.Document.Validation.VariablesAreInputTypes,
      Phase.Document.Validation.ArgumentsOfCorrectType,
      Phase.Document.Validation.KnownArgumentNames,
      Phase.Document.Validation.ProvidedNonNullArguments,
      Phase.Document.Validation.UniqueArgumentNames,
      Phase.Document.Validation.UniqueInputFieldNames,
      Phase.Document.Validation.FieldsOnCorrectType,
      Phase.Document.Validation.OnlyOneSubscription,
      # Check Validation
      {Phase.Document.Validation.Result, options},
      # Prepare for Execution
      Phase.Document.Arguments.Data,
      # Apply Directives
      Phase.Document.Directives,
      # Analyse Complexity
      # {Phase.Document.Complexity.Analysis, options},
      # {Phase.Document.Complexity.Result, options},
      # Execution
      {Fennel.Phase.Document.Execution.Subscription.Subscribe, options},
      {Fennel.Phase.Document.Execution.Cache.Fetch, options},
      {Fennel.Phase.Document.Execution.Run.Fetch, options},
      {Phase.Document.Execution.Resolution, options},
      {Fennel.Phase.Document.Execution.Cache.Store, options},
      # Format Result
      Phase.Document.Result,
      {Phase.Telemetry, Keyword.put(options, :event, [:execute, :operation, :stop])}
    ]
  end

  def options(client, options) do
    default_options = client.default_options()
    default_options
    |> Keyword.merge(options)
    |> Keyword.put_new_lazy(:variables, fn ->
      if ctx = Keyword.get(options, :context) do
        ctx.variables
      else
        %{}
      end
    end)
    |> Keyword.put_new(:add_typenames, Fennel.config_add_typenames())
    |> Absinthe.Pipeline.options()
  end
end