defmodule Absinthe.Phase.Document.Validation.ProvidedNonNullVariables do
  @moduledoc false

  # Validates document to ensure that all non-null variable definitions
  # are provided values.

  alias Absinthe.{Blueprint, Phase, Schema}

  use Absinthe.Phase

  @doc """
  Run the validation.
  """
  @spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t()
  def run(input, _options \\ []) do
    result =
      Blueprint.update_current(input, fn op ->
        Blueprint.prewalk(op, &handle_node(&1, input.schema))
      end)

    {:ok, result}
  end

  # Find variable definitions
  @spec handle_node(Blueprint.node_t(), Schema.t()) :: Blueprint.node_t()
  defp handle_node(
         %Blueprint.Document.VariableDefinition{
           type: %Blueprint.TypeReference.NonNull{},
           provided_value: nil
         } = node,
         _
       ) do
    node
    |> put_error(error(node))
  end

  defp handle_node(
         %Blueprint.Document.VariableDefinition{
           type: %Blueprint.TypeReference.NonNull{},
           provided_value: %Blueprint.Input.Null{}
         } = node,
         _
       ) do
    node
    |> put_error(error(node))
  end

  defp handle_node(node, _) do
    node
  end

  # Generate an error for variable definition
  @spec error(Blueprint.Document.VariableDefinition.t()) :: Phase.Error.t()
  defp error(node) do
    %Phase.Error{
      phase: __MODULE__,
      message: error_message(node.name),
      locations: [node.source_location]
    }
  end

  @doc """
  Generate the error message.
  """
  @spec error_message(String.t()) :: String.t()
  def error_message(variable_name) do
    ~s(Variable "#{variable_name}": Expected non-null, found null.)
  end
end
