diff --git a/v2/pkg/engine/plan/abstract_selection_rewriter_helpers.go b/v2/pkg/engine/plan/abstract_selection_rewriter_helpers.go index cf0b40db99..5586693a2a 100644 --- a/v2/pkg/engine/plan/abstract_selection_rewriter_helpers.go +++ b/v2/pkg/engine/plan/abstract_selection_rewriter_helpers.go @@ -544,14 +544,14 @@ func (r *fieldSelectionRewriter) getAllowedInterfaceMemberTypeNames(fieldRef int return nil, false, errors.New("unexpected error: field type name is not found in the upstream schema") } - // if typename of a field is not equal to the typename of the interface type + // when typename of a field is not equal to the typename of the interface type // then it should implement the interface type in the federated graph schema if interfaceTypeName != fieldTypeName { if slices.Contains(interfaceTypeNamesFromDefinition, fieldTypeName) { return []string{fieldTypeName}, false, nil } - // if it is not a member of the union type the config is corrupted + // if it doesn't implement an interface type the config is corrupted return nil, false, errors.New("unexpected error: field type do not implement the interface in the federated graph schema") } @@ -560,10 +560,36 @@ func (r *fieldSelectionRewriter) getAllowedInterfaceMemberTypeNames(fieldRef int return nil, false, errors.New("unexpected error: interface type definition not found in the upstream schema") } - // in case node kind is an interface type definition we just return the implementing types in this datasource + // when node kind is an interface type definition if interfaceNode.Kind == ast.NodeKindInterfaceTypeDefinition { - interfaceTypeNames, _ := r.upstreamDefinition.InterfaceTypeDefinitionImplementedByObjectWithNames(interfaceNode.Ref) + // we collect the implementing types in this datasource + localInterfaceTypeNames, _ := r.upstreamDefinition.InterfaceTypeDefinitionImplementedByObjectWithNames(interfaceNode.Ref) + + interfaceTypeNames := make([]string, 0, len(localInterfaceTypeNames)) + + // additionally, we need to check if implementing types are interface objects + // and replace such typename with concrete types + for _, typeName := range localInterfaceTypeNames { + isInterfaceObject := false + // when typeName is an interface object, we need to add concrete types instead of the interface object type name + for _, k := range r.dsConfiguration.FederationConfiguration().InterfaceObjects { + if k.InterfaceTypeName == typeName { + interfaceTypeNames = append(interfaceTypeNames, k.ConcreteTypeNames...) + isInterfaceObject = true + break + } + } + // when typename is not an interface object, we can add it as is + if !isInterfaceObject { + interfaceTypeNames = append(interfaceTypeNames, typeName) + } + } + + // sort implementing types to be able to compact them sort.Strings(interfaceTypeNames) + // remove possible consecutive duplicates + interfaceTypeNames = slices.Compact(interfaceTypeNames) + return interfaceTypeNames, false, nil } diff --git a/v2/pkg/engine/plan/abstract_selection_rewriter_test.go b/v2/pkg/engine/plan/abstract_selection_rewriter_test.go index cc920d13ac..11b5fc87b6 100644 --- a/v2/pkg/engine/plan/abstract_selection_rewriter_test.go +++ b/v2/pkg/engine/plan/abstract_selection_rewriter_test.go @@ -3727,6 +3727,93 @@ func TestInterfaceSelectionRewriter_RewriteOperation(t *testing.T) { }`, shouldRewrite: false, }, + { + name: "field is an interface with concrete type fragments. one of implementing types is interface object", + definition: ` + interface Named { + name: String! + } + + type User implements Named { + id: ID! + name: String! + surname: String! + } + + type Admin implements Account & Named { + id: ID! + name: String! + title: String! + } + + interface Account implements Named { + id: ID! + name: String! + title: String! + } + + type Query { + user: Named! + }`, + upstreamDefinition: ` + interface Named { + name: String! + } + + type User implements Named { + id: ID! + name: String! + surname: String! + } + + type Account implements Named @interfaceObject @key(fields: "id") { + id: ID! + name: String! + } + + type Query { + user: Named! + }`, + dsConfiguration: dsb(). + RootNode("Account", "id", "title"). + RootNode("User", "id", "name", "surname"). + WithMetadata(func(m *FederationMetaData) { + m.InterfaceObjects = []EntityInterfaceConfiguration{ + { + InterfaceTypeName: "Account", + ConcreteTypeNames: []string{"Admin", "User"}, + }, + } + }). + DS(), + fieldName: "user", + operation: ` + query { + __typename + user { + id + ... on Account { + ... on Admin { + title + } + } + } + }`, + expectedOperation: ` + query { + __typename + user { + ... on Admin { + id + title + } + ... on User { + id + } + } + }`, + shouldRewrite: true, + }, } for _, testCase := range testCases {