absinthe-graphql / absinthe

The GraphQL toolkit for Elixir
http://absinthe-graphql.org
Other
4.3k stars 527 forks source link

Running validation-only pipeline barfs on good data. #852

Closed ekobi closed 4 years ago

ekobi commented 4 years ago

This is an issue I reported on the Elixir forum, for which @benwilson512 provided a workaround. Wondering if either

or, alternatively

If submitting a bug, please provide the following:

Environment

Expected behavior

Absinthe.Pipeline.run() expected to validate provided executable GQL document and parameters against schema.

  def validate_graphql_docs() do
    options = [operation_name: "SomeMutation", variables: %{"message" => "string message"}]
    pipeline =
      My.Api.Schema
      |> Absinthe.Pipeline.for_document(options)
      |> Absinthe.Pipeline.without(Absinthe.Phase.Subscription.SubscribeSelf)
      |> Absinthe.Pipeline.without(Absinthe.Phase.Document.Execution.Resolution)
    case Absinthe.Pipeline.run(@badMutation, pipeline) do
      {:ok, %{execution: %{validation_errors: []}}, _} -> {:ok, []}
      {:ok, %{execution: %{validation_errors: validation_errors}}, _} -> {:error, validation_errors}
      other -> {:error, other}
    end
  end

Actual behavior

Does the right thing given invalid data/parameters, but barfs on well-formed documents thus:

     The following arguments were given to Absinthe.Phase.Document.Result.data/2:
         # 1
         nil
         # 2
         []
     Attempted function clauses (showing 5 out of 5):
         defp data(%{errors: [_ | _] = field_errors}, errors)
         defp data(%{value: nil}, errors)
         defp data(%{value: value, emitter: emitter}, errors)
         defp data(%{fields: fields}, errors)
         defp data(%{values: values}, errors)
     code: { status, errors } = GraphqlOps.validate_graphql_docs()
     stacktrace:
       (absinthe) lib/absinthe/phase/document/result.ex:50: Absinthe.Phase.Document.Result.data/2
       (absinthe) lib/absinthe/phase/document/result.ex:19: Absinthe.Phase.Document.Result.process/1
       (absinthe) lib/absinthe/phase/document/result.ex:11: Absinthe.Phase.Document.Result.run/2
       (absinthe) lib/absinthe/pipeline.ex:274: Absinthe.Pipeline.run_phase/3
       (pserver) lib/api/graphql_ops.ex:125: Pserver.Api.GraphqlOps.validate_graphql_docs/0
       test/pserver_api_test.exs:11: (test)
ekobi commented 4 years ago

Reproducing recommended workaround for the record:

The easiest work around is to just set a value manually, by introducing a custom phase:

    pipeline =
      My.Api.Schema
      |> Absinthe.Pipeline.for_document(options)
      |> Absinthe.Pipeline.without(Absinthe.Phase.Subscription.SubscribeSelf)
      |> Absinthe.Pipeline.without(Absinthe.Phase.Document.Execution.Resolution)
      |> Absinthe.Pipeline.insert_before(Absinthe.Phase.Document.Result, MyApp.AbsintheFakeResult)

defmodule MyApp.AbsintheFakeResult do
  def run(blueprint, _) do
    blueprint = put_in(blueprint.execution.result, %{value: nil})
    {:ok, blueprint}
  end
end
benwilson512 commented 4 years ago

Right, I think fundamentally the Result phase should just be made to work properly on documents with no execution result, which I think would be pretty easy.