absinthe-graphql / absinthe

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

pipeline_modifier not added during compile time, so dynamic types fail validation check #1331

Closed jeremyowensboggs closed 4 months ago

jeremyowensboggs commented 4 months ago

If submitting a bug, please provide the following:

Environment

Expected behavior

When I add a dynamic enum type via a @pipeline_modifier in my schema file, I expect that dynamic enum to be included when doing compile time validation.

What appears to be happening is that the module referenced in pipeline_modifier isn't compiled by the time the schema file is compiled, and so the pipeline_modifier isn't actually added to the pipeline during the compile phase, and so the generated types are missing when the validation checks are done.

Actual behavior

== Compilation error in file lib/blog_web/schema.ex ==
** (Absinthe.Schema.Error) Compilation failed:
---------------------------------------
## Locations
/Users/a80319307/dev/elixir/absinthe_issue/lib/blog_web/blog_types.ex:10

In field Status, :stage is not defined in your schema.

Types must exist if referenced.

    (absinthe 1.7.7) lib/absinthe/schema.ex:410: Absinthe.Schema.__after_compile__/2
    (stdlib 4.3.1.3) lists.erl:1350: :lists.foldl/3

Relevant Schema/Middleware Code

lib/blogweb/analytic_phase.ex

defmodule BlogWeb.AnalyticPhase do
  require Absinthe.Schema.Notation
  alias Absinthe.Blueprint
  alias Absinthe.Blueprint.Schema
  alias Absinthe.Pipeline
  alias Absinthe.Schema.Notation

  def pipeline(pipeline) do
    Pipeline.insert_after(pipeline, Absinthe.Phase.Schema.TypeImports, __MODULE__)
  end

  def run(blueprint = %Blueprint{}, _) do
    %{schema_definitions: [schema]} = blueprint

    ver = %Schema.EnumTypeDefinition{
      name: "stage",
      identifier: :stage,
      module: __MODULE__,
      __reference__: Notation.build_reference(__ENV__),
      values: [
        %Schema.EnumValueDefinition{
          identifier: :draft,
          value: :draft,
          name: "Draft",
          module: __MODULE__,
          __reference__: Notation.build_reference(__ENV__)
        },
        %Schema.EnumValueDefinition{
          identifier: :published,
          value: :published,
          name: "Published",
          module: __MODULE__,
          __reference__: Notation.build_reference(__ENV__)
        }
      ]
    }

    schema =
      Map.update!(schema, :type_definitions, fn type_definitions ->
        [ver | type_definitions]
      end)

    {:ok, %{blueprint | schema_definitions: [schema]}}
  end
end

lib/blog_web/blog_types.ex

defmodule BlogWeb.Schema.ContentTypes do
  use Absinthe.Schema.Notation

  def get_module(), do: __MODULE__

  object :post do
    field(:id, :id)
    field(:title, :string)
    field(:body, :string)
    field(:status, :stage)
  end
end

lib/blog_web/schema.ex

defmodule BlogWeb.Schema do
  use Absinthe.Schema
  alias BlogWeb.Resolvers
  alias BlogWeb.AnalyticPhase

  @pipeline_modifier AnalyticPhase

  import_types(BlogWeb.Schema.ContentTypes)

  query do
    @desc "Get all posts"
    field :posts, list_of(:post) do
      resolve(&Resolvers.Content.list_posts/3)
    end
  end
end

lib/blog_web/content.ex

defmodule BlogWeb.Resolvers.Content do
  def list_posts(_, _, _) do
    {:ok, ["Hello", "World"]}
  end
end
benwilson512 commented 4 months ago

Hey @jeremyowensboggs this was fixed on main. I have now pushed v1.7.8 with the fix.

jeremyowensboggs commented 4 months ago

The example above was distilled from a much larger project. When I update to 1.7.8 in that larger project, I get the following error: ** (ArgumentError) could not load module OrExWeb.Schemas.DynamicEnumCreationPhase due to reason :nofile (elixir 1.16.0) lib/code.ex:1829: Code.ensure_loaded!/1 (absinthe 1.7.8) lib/absinthe/schema.ex:382: anonymous fn/3 in Absinthe.Schema.apply_modifiers/3 (elixir 1.16.0) lib/enum.ex:2528: Enum."-reduce/3-lists^foldl/2-0-"/3 (absinthe 1.7.8) lib/absinthe/schema.ex:405: Absinthe.Schema.__after_compile__/2 (stdlib 4.3.1.3) lists.erl:1350: :lists.foldl/3

OrExWeb.Schemas.DynamicEnumCreationPhase does exist, and compiles fine in 1.7.6

benwilson512 commented 4 months ago

Hey @jeremyowensboggs can you try a clean build (rm -rf _build)? I'm not entirely sure what Absinthe is supposed to do here. The file needs to be loadable or we can't add its types.

jeremyowensboggs commented 4 months ago

Here is what I am doing as a workaround In the pipeline module

  @spec get_module :: __MODULE__
  def get_module, do: __MODULE__

In the schema

 @pipeline_modifier OrExWeb.Schemas.DynamicEnumCreationPhase.get_module()

And that seems to make sure that the compiler is aware of the pipeline file.

Note - now elixir 1.17.2 instead of 1.16 as stated in the original report.

jeremyowensboggs commented 4 months ago

Updated my example project to 1.17.1, and removed _build. I get the error on the first compile, but not on the subsequent builds.

Remove _build again, and the error returns, and disappears when another mix compile is issued.