diff --git a/lib/graphql/execution/interpreter/runtime.rb b/lib/graphql/execution/interpreter/runtime.rb index 1c470e81e2..b55e6cfed7 100644 --- a/lib/graphql/execution/interpreter/runtime.rb +++ b/lib/graphql/execution/interpreter/runtime.rb @@ -685,12 +685,16 @@ def continue_field(path, value, owner_type, field, current_type, ast_node, next_ set_result(selection_result, result_name, r) r when "UNION", "INTERFACE" - resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path) - resolved_value ||= value + resolved_type_or_lazy = resolve_type(current_type, value, path) + after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type_result| + if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2 + resolved_type, resolved_value = resolved_type_result + else + resolved_type = resolved_type_result + resolved_value = value + end - after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type| possible_types = query.possible_types(current_type) - if !possible_types.include?(resolved_type) parent_type = field.owner_type err_class = current_type::UnresolvedTypeError diff --git a/lib/graphql/schema.rb b/lib/graphql/schema.rb index bc44611ed1..4f97bd1433 100644 --- a/lib/graphql/schema.rb +++ b/lib/graphql/schema.rb @@ -754,13 +754,21 @@ def handle_or_reraise(context, err) # rubocop:disable Lint/DuplicateMethods module ResolveTypeWithType def resolve_type(type, obj, ctx) - first_resolved_type, resolved_value = if type.is_a?(Module) && type.respond_to?(:resolve_type) + maybe_lazy_resolve_type_result = if type.is_a?(Module) && type.respond_to?(:resolve_type) type.resolve_type(obj, ctx) else super end - after_lazy(first_resolved_type) do |resolved_type| + after_lazy(maybe_lazy_resolve_type_result) do |resolve_type_result| + if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2 + resolved_type = resolve_type_result[0] + resolved_value = resolve_type_result[1] + else + resolved_type = resolve_type_result + resolved_value = obj + end + if resolved_type.nil? || (resolved_type.is_a?(Module) && resolved_type.respond_to?(:kind)) if resolved_value [resolved_type, resolved_value] diff --git a/lib/graphql/schema/member/has_arguments.rb b/lib/graphql/schema/member/has_arguments.rb index 601fc30873..cff260b6aa 100644 --- a/lib/graphql/schema/member/has_arguments.rb +++ b/lib/graphql/schema/member/has_arguments.rb @@ -323,8 +323,14 @@ def authorize_application_object(argument, id, context, loaded_application_objec end # Double-check that the located object is actually of this type # (Don't want to allow arbitrary access to objects this way) - resolved_application_object_type = context.schema.resolve_type(argument.loads, application_object, context) - context.schema.after_lazy(resolved_application_object_type) do |application_object_type| + maybe_lazy_resolve_type = context.schema.resolve_type(argument.loads, application_object, context) + context.schema.after_lazy(maybe_lazy_resolve_type) do |resolve_type_result| + if resolve_type_result.is_a?(Array) && resolve_type_result.size == 2 + application_object_type, application_object = resolve_type_result + else + application_object_type = resolve_type_result + # application_object is already assigned + end possible_object_types = context.warden.possible_types(argument.loads) if !possible_object_types.include?(application_object_type) err = GraphQL::LoadApplicationObjectFailedError.new(argument: argument, id: id, object: application_object) diff --git a/spec/graphql/schema/union_spec.rb b/spec/graphql/schema/union_spec.rb index 834276c2b2..bd669a806d 100644 --- a/spec/graphql/schema/union_spec.rb +++ b/spec/graphql/schema/union_spec.rb @@ -67,7 +67,7 @@ assert_equal "Fragment on Ensemble can't be spread inside PerformingAct", res.to_h["errors"].first["message"] end - it "can cast the object after resolving the type" do + describe "two-value type resolution" do Box = Struct.new(:value) class Schema < GraphQL::Schema @@ -75,16 +75,24 @@ class A < GraphQL::Schema::Object field :a, String, null: false, method: :itself end + class B < GraphQL::Schema::Object + field :b, String, method: :itself + end + class MyUnion < GraphQL::Schema::Union - possible_types A + possible_types A, B def self.resolve_type(object, ctx) - [A, object.value] + if object.value == "return-nil" + [B, nil] + else + [A, object.value] + end end end class Query < GraphQL::Schema::Object - field :my_union, MyUnion, null: false + field :my_union, MyUnion def my_union Box.new(context[:value]) @@ -94,19 +102,38 @@ def my_union query(Query) end - query_str = <<-GRAPHQL - { - myUnion { - ... on A { a } + it "can cast the object after resolving the type" do + + query_str = <<-GRAPHQL + { + myUnion { + ... on A { a } + } } - } - GRAPHQL + GRAPHQL - res = Schema.execute(query_str, context: { value: "unwrapped" }) + res = Schema.execute(query_str, context: { value: "unwrapped" }) - assert_equal({ - 'data' => { 'myUnion' => { 'a' => 'unwrapped' } } - }, res.to_h) + assert_equal({ + 'data' => { 'myUnion' => { 'a' => 'unwrapped' } } + }, res.to_h) + end + + it "uses `nil` when returned from resolve_type" do + query_str = <<-GRAPHQL + { + myUnion { + ... on B { b } + } + } + GRAPHQL + + res = Schema.execute(query_str, context: { value: "return-nil" }) + + assert_equal({ + 'data' => { 'myUnion' => { 'b' => nil } } + }, res.to_h) + end end end