Validation-only pipeline for Absinthe?

Hi all. Wondering if anyone can help me here. Trying to leverage Absinthe to validate GraphQL executable documents, but without executing the resolvers on success:

  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

This works as expected when I feed it bad documents or invalid variables, but barfs on good data:

     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)

Any suggestions on how to either fix my pipeline, or solve in a different way? Thanks.

2 Likes

Does this help https://hexdocs.pm/absinthe/Absinthe.Plugin.html#c:before_resolution/1?

Hey @ekobi! As you’re noting, while it’s entirely possible to run validation without doing execution, turning the validation results into a standard response with the Document.Result phase doesn’t seem to work.

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

This should let it match one of the result clauses. Feel free to open up an issue on Absinthe around handling this in a more first class way.

4 Likes

Wow, that was fast! And it works, too :> Thanks, @benwilson512

I’ll file an issue request as suggested presently.