Errors programmatically generating Absinthe input objects (InputObjectTypeDefinition)

I’ve got most of the pieces of this thing together… I thought. I am attempting to programmatically generate a handful of repetitive Absinthe schemas programmatically. Just trying to paste a struct into iex causes an error.

iex> %Absinthe.Blueprint.Schema.InputObjectTypeDefinition{
  description: nil,
  fields: [],
  identifier: :foo_bar,
  module: MyAppWeb.GraphQLSchema,
  name: "FooBar"
}

But this generates errors like these:

%Inspect.Error{
  message: "got RuntimeError with message \"No type found for identifier :string in []\\n\" while inspecting %{__private__: [], __reference__: %{}, __struct__: Absinthe.Blueprint.Schema.InputObjectTypeDefinition, description: \"Some custom input thing.\", directives: [], errors: [], fields: [%Absinthe.Blueprint.Schema.FieldDefinition{__private__: [], _ ...

I’ve tried fiddling around with omitting certain fields, but so far, but there always seems to be an error.

I haven’t run into an error like this in Elixir before where I was unable to define a struct, so I’m not sure what’s going on. I looked through the source code for the defmacro input_object but I couldn’t follow what was going on there either. Is there a better way to generate structs of this type that won’t set-off alarm bells?

Hey @fireproofsocks this is a product of a perhaps regrettable design decision absinthe did with the blueprint schema structs, where in general inspecting elements of a schema tries to render them as SDL. This ends up generally only working if they’re part of a full schema so that all of the types can be found. It’s ended up being pretty buggy and could use some changes.

The work around if you’re developing with these is to simply IO.inspect(value, structs: false) to see the “raw” map.

1 Like

Wow – I didn’t even know such a thing was possible with Elixir. How do you make code execute just by declaring a struct? Is this because of an implementation of the Inspect protocol?

Is there a related issue that discusses the feature? Perhaps blueprint inside telemetry events comes out as Inspect.Error when in IEx · Issue #1185 · absinthe-graphql/absinthe · GitHub ?

Between the compile-time errors and Javascript errors, I was able sniff out my misspellings and such, but is there a better way to test schema generation?

Yes, it’s basically that issue.

What you can do is run this IEx.configure(inspect: [pretty: false]) in IEx prior to declaring the struct. This will print it like a usual struct instead of the prettified form and should fix the bug.

It’s also possible to have a .iex.exs file if you want to change the config every time you run IEx. See IEx — IEx v1.14.2 for more information.

That isn’t quite what’s happening here. You’re in IEX so actually two things are happening:

  1. A struct is declared
  2. the struct value is returned to iex as the last value of that line of the REPL, so IEX calls inspect(value) on it.

It’s step 2 that is executing code according to the Inspect protocol.

You’re trying to test it for validity? For sure one thing I’d make sure to do is insert your types at the right moment in the pipeline so that Absinthe’s schema validations can still run. Generally I recommend:

    pipeline
    |> Absinthe.Pipeline.insert_after(Absinthe.Phase.Schema.TypeImports, YourModuleHere)

This has you adding your types after all the regular type imports have happened, but before all of Absinthe’s schema validations.

Thanks Ben, that’s helpful. I ended up assembling a list of all my input objects and appending them to the end of the SchemaDefinition’s :type_definitions by manipulating the Blueprint contents directly, e.g. something like this:

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

    updated_schema = %{
      main_schema
      | type_definitions: type_definitions ++ generate_list_of_custom_schemas_here()
    }

    blueprint = %{blueprint | schema_definitions: [updated_schema]}

    {:ok, blueprint}
  end

This seems to work, but it admittedly feels inelegant. I’m not sure if there’s any guarantee that a blueprint contains only a single SchemaDefinition, for example.

1 Like

This is actually 100% the thing to do. This is for example what Absinthe.Relay’s phase does: absinthe_relay/lib/absinthe/relay/schema/phase.ex at main · absinthe-graphql/absinthe_relay · GitHub

Good news! The rest of Absinthe’s pipeline takes care of that for you. By injecting your module into the pipeline after TypeImports all of the other phases here then run after to validate things: absinthe/lib/absinthe/pipeline.ex at main · absinthe-graphql/absinthe · GitHub