diff --git a/lib/graphql/schema/input_object.rb b/lib/graphql/schema/input_object.rb index 981223d714..e59be672c7 100644 --- a/lib/graphql/schema/input_object.rb +++ b/lib/graphql/schema/input_object.rb @@ -70,11 +70,10 @@ def self.authorized?(obj, value, ctx) end def self.one_of - if !@one_of - if (required_arg = all_argument_definitions.find { |arg| arg.type.non_null? }) - raise ArgumentError, "`one_of` may not be used with required arguments -- add `required: false` to `argument #{required_arg.keyword.inspect}` to use `one_of`" + if !one_of? + if all_argument_definitions.any? { |arg| arg.type.non_null? } + raise ArgumentError, "`one_of` may not be used with required arguments -- add `required: false` to argument definitions to use `one_of`" end - @one_of = true directive(GraphQL::Schema::Directive::OneOf) end end @@ -123,8 +122,13 @@ def to_kwargs class << self def argument(*args, **kwargs, &block) argument_defn = super(*args, **kwargs, &block) - if one_of? && argument_defn.type.non_null? - raise ArgumentError, "`one_of` may not be used with required arguments -- add `required: false` to `argument #{argument_defn.keyword.inspect}` to use `one_of`" + if one_of? + if argument_defn.type.non_null? + raise ArgumentError, "Argument '#{argument_defn.path}' must be nullable because it is part of a OneOf type, add `required: false`." + end + if argument_defn.default_value? + raise ArgumentError, "Argument '#{argument_defn.path}' cannot have a default value because it is part of a OneOf type, remove `default_value: ...`." + end end # Add a method access method_name = argument_defn.keyword diff --git a/spec/graphql/schema/directive/one_of_spec.rb b/spec/graphql/schema/directive/one_of_spec.rb new file mode 100644 index 0000000000..1e4b828bee --- /dev/null +++ b/spec/graphql/schema/directive/one_of_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true +require "spec_helper" + +describe GraphQL::Schema::Directive::OneOf do + let(:schema) do + this = self + output_type = Class.new(GraphQL::Schema::Object) do + graphql_name "OneOfOutput" + + field :string, GraphQL::Types::String + field :int, GraphQL::Types::Int + end + + query_type = Class.new(GraphQL::Schema::Object) do + graphql_name "Query" + + field :one_of_field, output_type, null: false do + argument :one_of_arg, this.one_of_input_object + end + + def one_of_field(one_of_arg:) + one_of_arg + end + end + + Class.new(GraphQL::Schema) do + query(query_type) + end + end + + let(:one_of_input_object) do + Class.new(GraphQL::Schema::InputObject) do + graphql_name "OneOfInputObject" + directive GraphQL::Schema::Directive::OneOf + + argument :int, GraphQL::Types::Int + argument :string, GraphQL::Types::String + end + end + + describe "defining oneOf input objects" do + describe "with a non-null argument" do + let(:one_of_input_object) do + Class.new(GraphQL::Schema::InputObject) do + graphql_name "OneOfInputObject" + directive GraphQL::Schema::Directive::OneOf + + argument :int, GraphQL::Types::Int, required: true # rubocop:disable GraphQL/DefaultRequiredTrue + argument :string, GraphQL::Types::String + end + end + + it "raises an error" do + error = assert_raises(ArgumentError) { schema } + expected_message = "Argument 'OneOfInputObject.int' must be nullable because it is part of a OneOf type, add `required: false`." + assert_equal(expected_message, error.message) + end + end + + describe "when an argument has a default" do + let(:one_of_input_object) do + Class.new(GraphQL::Schema::InputObject) do + graphql_name "OneOfInputObject" + directive GraphQL::Schema::Directive::OneOf + + argument :int, GraphQL::Types::Int, default_value: 1, required: false + argument :string, GraphQL::Types::String, required: false + end + end + + it "raises an error" do + error = assert_raises(ArgumentError) { schema } + expected_message = "Argument 'OneOfInputObject.int' cannot have a default value because it is part of a OneOf type, remove `default_value: ...`." + assert_equal(expected_message, error.message) + end + end + end +end diff --git a/spec/graphql/schema/input_object_spec.rb b/spec/graphql/schema/input_object_spec.rb index 581c399ff0..9167ac0a0c 100644 --- a/spec/graphql/schema/input_object_spec.rb +++ b/spec/graphql/schema/input_object_spec.rb @@ -1183,21 +1183,23 @@ def f(a:) it "doesn't work without required: false" do err1 = assert_raises ArgumentError do Class.new(GraphQL::Schema::InputObject) do + graphql_name "OneOfThing" argument :arg_1, GraphQL::Types::Int one_of end end - assert_equal "`one_of` may not be used with required arguments -- add `required: false` to `argument :arg_1` to use `one_of`", err1.message + assert_equal "`one_of` may not be used with required arguments -- add `required: false` to argument definitions to use `one_of`", err1.message err2 = assert_raises ArgumentError do Class.new(GraphQL::Schema::InputObject) do + graphql_name "OneOfThing" one_of argument :arg_2, GraphQL::Types::Int end end - assert_equal "`one_of` may not be used with required arguments -- add `required: false` to `argument :arg_2` to use `one_of`", err2.message + assert_equal "Argument 'OneOfThing.arg2' must be nullable because it is part of a OneOf type, add `required: false`.", err2.message end it "allows queries with only one value" do