You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
If you try using a union type to restrict what something loads, it doesn't work if that union type isn't also exposed as a field somewhere.:
# Union typeclassTypes::MyUnionpossible_typesTypes::TypeA,Types::TypeBend# Mutation typeclassMutations::DestroyMyUnionargument:object_id,ID,required: true,loads: Types::MyUnionfield:success,Boolean,null: false# un-commenting the line below makes things work as expected# field :object, Types::MyUnion, null: falsedefresolve(object:)object.destroyendend
If you are using a globally unique ID to describe objects, than any globably unique ID will be accepted in the above scenario. If the union type is exposed as a field anywhere or added to orphaned types (only works in older versions, extra types doesn't work) things work as expected in that it will filter out unwanted ones.
This bug may also exist for interfaces but I haven't gotten around to testing that yet. I'll comment on this issue when I do.
Versions
graphql version: 2.3.14 rails (or other framework): 7.0.8.4
GraphQL schema
Here is a test schema with rspec tests inlined to illustrate the issue. Note how the field for the union is commented out.
# frozen_string_literal: truerequire"spec_helper"moduleTypesmoduleTestInterfaceTypeincludeTypes::BaseInterfacefield:id,String,null: falsefield:hello,String,null: falseendclassTestConcreteType1 < Types::BaseObjectgraphql_name"TestConcreteType1"implementsTestInterfaceTypeskip_authorization!global_id_field:iddefhello"hello from concrete type 1"endendclassTestConcreteType2 < Types::BaseObjectgraphql_name"TestConcreteType2"skip_authorization!global_id_field:idendclassTestConcreteType3 < Types::BaseObjectgraphql_name"TestConcreteType3"implementsTestInterfaceTypeskip_authorization!global_id_field:iddefhello"hello from concrete type 3"endendclassTestUnionType < BaseUnionpossible_typesTestConcreteType1,TestConcreteType2endclassTestQueryType < Types::BaseObjectskip_authorization!field:concrete_type_1,Types::TestConcreteType1,null: falsedoargument:concrete_type_1_id,ID,required: true,loads: Types::TestConcreteType1end# field :union_type, Types::TestUnionType, null: false do# argument :union_type_id, ID, required: true, loads: Types::TestUnionType# endfield:interface_type,Types::TestInterfaceType,null: falsedoargument:interface_type_id,ID,required: true,loads: Types::TestInterfaceTypeenddefconcrete_type_1(concrete_type_1:)concrete_type_1enddefunion_type(union_type:)union_typeenddefinterface_type(interface_type:)interface_typeendendendmoduleMutationsclassMutateUnionType < Mutations::BaseMutationargument:union_type_id,ID,required: true,loads: Types::TestUnionTypefield:success,Boolean,null: falsedefauthorized?(union_type:)trueenddefresolve(union_type:){success: union_type.present?}endendendmoduleTypesclassTestMutationType < Types::BaseReturnObjectfield:mutate_union_type,mutation: Mutations::MutateUnionTypeendendclassTestBaseSchema < GraphQL::Schemaquery(Types::TestQueryType)mutation(Types::TestMutationType)extra_types[Types::TestUnionType]defself.id_from_object(object,type_definition,_query_ctx)GraphQL::Schema::UniqueWithinType.encode(type_definition.name,object.id)enddefself.object_from_id(id,_query_ctx)gql_type_name,item_id=GraphQL::Schema::UniqueWithinType.decode(id)OpenStruct.new(type: gql_type_name.constantize,id: item_id)enddefself.resolve_type(type,object,ctx)object.typeendenddescribeBaseSchemadodefdescribed_classTestBaseSchemaenddescribe"locking down of load to only allowed types"doit"won't load objects that don't match type"doconcrete_type_1=OpenStruct.new(type: Types::TestConcreteType1,id: 1)concrete_type_1_id=described_class.id_from_object(concrete_type_1,Types::TestConcreteType1,{})query=<<~GQL query { concreteType1(concreteType1Id: "#{concrete_type_1_id}") { id hello } } GQLresult=described_class.execute(query)expect(result.dig("data","concreteType1","id")).toeq(concrete_type_1_id)concrete_type_2=OpenStruct.new(type: Types::TestConcreteType2,id: 2)concrete_type_2_id=described_class.id_from_object(concrete_type_2,Types::TestConcreteType2,{})query=<<~GQL query { concreteType1(concreteType1Id: "#{concrete_type_2_id}") { id hello } } GQLresult=described_class.execute(query)expect(result.dig("data","concreteType1","id")).toeq(nil)endenddescribe"locking down union"doit"won't load objects that don't match type"doconcrete_type_1=OpenStruct.new(type: Types::TestConcreteType1,id: 1)concrete_type_1_id=described_class.id_from_object(concrete_type_1,Types::TestConcreteType1,{})query=<<~GQL query { unionType(unionTypeId: "#{concrete_type_1_id}") { __typename ...on TestConcreteType1 { id hello } } } GQLresult=described_class.execute(query)expect(result.dig("data","unionType","id")).toeq(concrete_type_1_id)concrete_type_3=OpenStruct.new(type: Types::TestConcreteType3,id: 3)concrete_type_3_id=described_class.id_from_object(concrete_type_3,Types::TestConcreteType3,{})query=<<~GQL query { unionType(unionTypeId: "#{concrete_type_3_id}") { __typename ...on TestConcreteType2 { id } } } GQLresult=described_class.execute(query)expect(result.dig("data","unionType","id")).toeq(nil)endenddescribe"locking down interface"doit"won't load objects that don't match type"doconcrete_type_1=OpenStruct.new(type: Types::TestConcreteType1,id: 1)concrete_type_1_id=described_class.id_from_object(concrete_type_1,Types::TestConcreteType1,{})query=<<~GQL query { interfaceType(interfaceTypeId: "#{concrete_type_1_id}") { id hello } } GQLresult=described_class.execute(query)expect(result.dig("data","interfaceType","id")).toeq(concrete_type_1_id)concrete_type_2=OpenStruct.new(type: Types::TestConcreteType2,id: 2)concrete_type_2_id=described_class.id_from_object(concrete_type_2,Types::TestConcreteType2,{})query=<<~GQL query { interfaceType(interfaceTypeId: "#{concrete_type_2_id}") { id hello } } GQLresult=described_class.execute(query)expect(result.dig("data","interfaceType","id")).toeq(nil)endenddescribe"locking down union type mutation"doit"won't load objects that don't match type",focus: truedoconcrete_type_1=OpenStruct.new(type: Types::TestConcreteType1,id: 1)concrete_type_1_id=described_class.id_from_object(concrete_type_1,Types::TestConcreteType1,{})query=<<~GQL mutation { mutateUnionType(input: {unionTypeId: "#{concrete_type_1_id}"}) { success } } GQLresult=described_class.execute(query)expect(result.dig("data","mutateUnionType","success")).toeq(true)concrete_type_3=OpenStruct.new(type: Types::TestConcreteType3,id: 3)concrete_type_3_id=described_class.id_from_object(concrete_type_3,Types::TestConcreteType3,{})query=<<~GQL mutation { mutateUnionType(input: {unionTypeId: "#{concrete_type_3_id}"}) { success } } GQLresult=described_class.execute(query)expect(result.dig("data","mutateUnionType","success")).toeq(nil)endendend
GraphQL query
See test steps for mutation example
Steps to reproduce
Steps to reproduce the behavior
Expected behavior
I would expect a union type to behave the same in loading an object whether it was exposed as a field or not.
Actual behavior
The union type doesn't restrict loading types that aren't part of the union like I would expect unless it is also declared as the return type on a field or declared in orphaned types (on older versions).
The text was updated successfully, but these errors were encountered:
Hey, thanks for the detailed write-up. I definitely agree that this should be supported one way or another, I'll just have to take a look at how it can be accomplished. This method is supposed to return true for types in cases like this, but maybe it's not:
Describe the bug
If you try using a union type to restrict what something loads, it doesn't work if that union type isn't also exposed as a field somewhere.:
If you are using a globally unique ID to describe objects, than any globably unique ID will be accepted in the above scenario. If the union type is exposed as a field anywhere or added to orphaned types (only works in older versions, extra types doesn't work) things work as expected in that it will filter out unwanted ones.
This bug may also exist for interfaces but I haven't gotten around to testing that yet. I'll comment on this issue when I do.
Versions
graphql
version: 2.3.14rails
(or other framework): 7.0.8.4GraphQL schema
Here is a test schema with rspec tests inlined to illustrate the issue. Note how the field for the union is commented out.
GraphQL query
See test steps for mutation example
Steps to reproduce
Steps to reproduce the behavior
Expected behavior
I would expect a union type to behave the same in loading an object whether it was exposed as a field or not.
Actual behavior
The union type doesn't restrict loading types that aren't part of the union like I would expect unless it is also declared as the return type on a field or declared in orphaned types (on older versions).
The text was updated successfully, but these errors were encountered: