Skip to content

directive arguments validation for ruby hashes not working since v2.5.8 #5424

@juliaren

Description

@juliaren

Describe the bug

getting an error: GraphQL::Schema::Directive::InvalidArgumentError: @documentation.options on <my_field> is invalid ([{:key=>"1", :value=>"1"}]): Expected value to not be null

I have a documentation Directive with a single argument: argument :options, [DirectiveOption, null: true], required: false with a custom input object DirectiveOption defined as:

class DirectiveOption < GraphQL::Schema::InputObject
    argument :key, String, required: true
    argument :value, String, required: true
 end

in another module, I attach this directive to relevant Schema objects by: directive(Directives::SpectaQL, options: directive_options) where directive_options is an array of key-value pairs like: [{ :key => '1', :value =>'1' }, { :key => '2', :value => '2' }]. I attempted a fix by using string keys instead of symbol keys: [{ 'key' => '1', 'value' =>'1' }, { 'key' => '2', 'value' => '2' }] but still got the same error. So far the only workaround (not ideal) is not using a custom input object and using a scalar type (e.g. string) instead.

Versions

graphql version: 2.5.11
rails (or other framework):
other applicable versions (graphql-batch, etc)

GraphQL schema

Include relevant types and fields (in Ruby is best, in GraphQL IDL is ok). Any custom extensions, etc?

# frozen_string_literal: true

module Directives
  class SpectaQL < GraphQL::Schema::Directive
    graphql_name 'documentation'

    class DirectiveOption < GraphQL::Schema::InputObject
      graphql_name 'DocumentationOption'

      argument :key, String, required: true
      argument :value, String, required: true
    end

    argument :options, [DirectiveOption, null: true], required: false
    locations QUERY, MUTATION, SUBSCRIPTION, FIELD,
              FRAGMENT_DEFINITION, FRAGMENT_SPREAD, INLINE_FRAGMENT, VARIABLE_DEFINITION,
              SCHEMA, SCALAR, OBJECT, FIELD_DEFINITION, ARGUMENT_DEFINITION,
              INTERFACE, UNION, ENUM, ENUM_VALUE, INPUT_OBJECT, INPUT_FIELD_DEFINITION
  end
end

# frozen_string_literal: true

module HasSpectaQLDirective
  attr_reader :spectaql

  def spectaql=(options)
    directive_options = options.map do |k, v|
      case k
      when :1
        { key: '1', value: v.to_s }
      when :2
        { key: '2', value: v.to_s }
      when :3
        { key: '3', value: v.to_s }
      else
        raise ArgumentError.new "Invalid key for spectaql= :: #{k}"
      end
    end

    if directive_options.present?
      directive(Directives::SpectaQL, options: directive_options)
    end
  end
end

GraphQL query

N/A

Steps to reproduce

Steps to reproduce the behavior

  • build schema with command graphql:schema:idl

Expected behavior

  • schema builds successfully with directive attached

Actual behavior

getting an error: GraphQL::Schema::Directive::InvalidArgumentError: @documentation.options on <my_field> is invalid ([{:key=>"1", :value=>"1"}]): Expected value to not be null. see full backtrace here:

Click to view exception backtrace
GraphQL::Schema::Directive::InvalidArgumentError: @documentation.options on <my_field> is invalid ([{:key=>"1", :value=>"1"}]): Expected value to not be null
/bundle/ruby/3.0.0/gems/graphql-2.5.11/lib/graphql/schema/directive.rb:147:in `block in initialize'
/bundle/ruby/3.0.0/gems/graphql-2.5.11/lib/graphql/schema/directive.rb:131:in `each'
/bundle/ruby/3.0.0/gems/graphql-2.5.11/lib/graphql/schema/directive.rb:131:in `initialize'
/bundle/ruby/3.0.0/gems/graphql-2.5.11/lib/graphql/schema/member/has_directives.rb:43:in `new'
/bundle/ruby/3.0.0/gems/graphql-2.5.11/lib/graphql/schema/member/has_directives.rb:43:in `add_directive'
/bundle/ruby/3.0.0/gems/graphql-2.5.11/lib/graphql/schema/member/has_directives.rb:24:in `directive'
/app/graphql/concerns/has_spectaql_directive.rb:31:in `spectaql='
/app/graphql/types/base_field.rb:143:in `initialize'
/bundle/ruby/3.0.0/gems/graphql-2.5.11/lib/graphql/schema/field.rb:155:in `new'
/bundle/ruby/3.0.0/gems/graphql-2.5.11/lib/graphql/schema/field.rb:155:in `from_options'
/bundle/ruby/3.0.0/gems/graphql-2.5.11/lib/graphql/schema/member/has_fields.rb:12:in `field'
/bundle/ruby/3.0.0/gems/activesupport-6.1.7.10/lib/active_support/option_merger.rb:34:in `invoke_method'
/bundle/ruby/3.0.0/gems/activesupport-6.1.7.10/lib/active_support/option_merger.rb:28:in `method_missing'
/app/graphql/types/delivery_stats_record.rb:6:in `block in <class:DeliveryStatsRecord>'
/bundle/ruby/3.0.0/gems/activesupport-6.1.7.10/lib/active_support/core_ext/object/with_options.rb:80:in `instance_eval'
/bundle/ruby/3.0.0/gems/activesupport-6.1.7.10/lib/active_support/core_ext/object/with_options.rb:80:in `with_options'
/app/graphql/types/delivery_stats_record.rb:5:in `<class:DeliveryStatsRecord>'
/app/graphql/types/delivery_stats_record.rb:2:in `<module:Types>'
/app/graphql/types/delivery_stats_record.rb:1:in `<main>'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/zeitwerk-2.6.18/lib/zeitwerk/kernel.rb:26:in `require'
/app/graphql/types/delivery_stats.rb:6:in `block in <class:DeliveryStats>'
/bundle/ruby/3.0.0/gems/activesupport-6.1.7.10/lib/active_support/core_ext/object/with_options.rb:80:in `instance_eval'
/bundle/ruby/3.0.0/gems/activesupport-6.1.7.10/lib/active_support/core_ext/object/with_options.rb:80:in `with_options'
/app/graphql/types/delivery_stats.rb:5:in `<class:DeliveryStats>'
/app/graphql/types/delivery_stats.rb:2:in `<module:Types>'
/app/graphql/types/delivery_stats.rb:1:in `<main>'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/zeitwerk-2.6.18/lib/zeitwerk/kernel.rb:26:in `require'
/app/graphql/types/conversion_pixel.rb:17:in `block (2 levels) in <class:ConversionPixel>'
/bundle/ruby/3.0.0/gems/activesupport-6.1.7.10/lib/active_support/core_ext/object/with_options.rb:80:in `instance_eval'
/bundle/ruby/3.0.0/gems/activesupport-6.1.7.10/lib/active_support/core_ext/object/with_options.rb:80:in `with_options'
/app/graphql/types/conversion_pixel.rb:14:in `block in <class:ConversionPixel>'
/bundle/ruby/3.0.0/gems/activesupport-6.1.7.10/lib/active_support/core_ext/object/with_options.rb:80:in `instance_eval'
/bundle/ruby/3.0.0/gems/activesupport-6.1.7.10/lib/active_support/core_ext/object/with_options.rb:80:in `with_options'
/app/graphql/types/conversion_pixel.rb:13:in `<class:ConversionPixel>'
/app/graphql/types/conversion_pixel.rb:4:in `<module:Types>'
/app/graphql/types/conversion_pixel.rb:3:in `<main>'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/zeitwerk-2.6.18/lib/zeitwerk/kernel.rb:26:in `require'
/app/graphql/types/interfaces/pixel.rb:219:in `<module:Pixel>'
/app/graphql/types/interfaces/pixel.rb:4:in `<module:Interfaces>'
/app/graphql/types/interfaces/pixel.rb:3:in `<main>'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/zeitwerk-2.6.18/lib/zeitwerk/kernel.rb:26:in `require'
/app/graphql/types/account.rb:17:in `<class:Account>'
/app/graphql/types/account.rb:4:in `<module:Types>'
/app/graphql/types/account.rb:3:in `<main>'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/zeitwerk-2.6.18/lib/zeitwerk/kernel.rb:26:in `require'
/app/graphql/mutations/update_account.rb:15:in `<class:UpdateAccount>'
/app/graphql/mutations/update_account.rb:2:in `<module:Mutations>'
/app/graphql/mutations/update_account.rb:1:in `<main>'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'`
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'`
/bundle/ruby/3.0.0/gems/zeitwerk-2.6.18/lib/zeitwerk/kernel.rb:26:in `require'
/app/graphql/types/mutation_type.rb:6:in `<class:MutationType>'
/app/graphql/types/mutation_type.rb:2:in `<module:Types>'
/app/graphql/types/mutation_type.rb:1:in `<main>'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/zeitwerk-2.6.18/lib/zeitwerk/kernel.rb:26:in `require'
/app/graphql/schema.rb:4:in `<class:SASchema>'
/app/graphql/schema.rb:3:in `<main>'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/zeitwerk-2.6.18/lib/zeitwerk/kernel.rb:26:in `require'
/app/graphql/public_schema.rb:3:in `<main>'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/bootsnap-1.18.4/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/bundle/ruby/3.0.0/gems/zeitwerk-2.6.18/lib/zeitwerk/kernel.rb:26:in `require'
/lib/tasks/graphql.rake:6:in `block in <main>'
/bundle/ruby/3.0.0/gems/graphql-2.5.11/lib/graphql/rake_task.rb:101:in `write_outfile'
/bundle/ruby/3.0.0/gems/graphql-2.5.11/lib/graphql/rake_task.rb:147:in `block (3 levels) in define_task'
/bundle/ruby/3.0.0/gems/rake-13.2.1/exe/rake:27:in `<top (required)>'
Tasks: TOP => graphql:schema:idl
(See full trace by running task with --trace)

Additional context

error occurred during graphql-ruby gem upgrade from 2.2.17 -> 2.5.x. Through trial and error confirmed that the last working version is v2.5.7. So in v2.5.8 this bug was introduced. relevant PR found: #5377

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions