diff --git a/v2/pkg/engine/plan/abstract_selection_rewriter.go b/v2/pkg/engine/plan/abstract_selection_rewriter.go index c529cf6dd5..8320d570d6 100644 --- a/v2/pkg/engine/plan/abstract_selection_rewriter.go +++ b/v2/pkg/engine/plan/abstract_selection_rewriter.go @@ -472,6 +472,13 @@ func (r *fieldSelectionRewriter) interfaceFieldSelectionNeedsRewrite(selectionSe if !r.allEntitiesHaveFieldsAsRootNode(entitiesWithoutFragment, selectionSetInfo.fields) { return true } + + // check if any implementing type has requiresConfiguration for one of the requested fields + if slices.ContainsFunc(entityNames, func(entityName string) bool { + return r.hasRequiresConfigurationForField(entityName, selectionSetInfo.fields) + }) { + return true + } } if selectionSetInfo.hasFields && selectionSetInfo.hasInlineFragmentsOnObjects { diff --git a/v2/pkg/engine/plan/abstract_selection_rewriter_test.go b/v2/pkg/engine/plan/abstract_selection_rewriter_test.go index 30ab42dbd6..486193ca6f 100644 --- a/v2/pkg/engine/plan/abstract_selection_rewriter_test.go +++ b/v2/pkg/engine/plan/abstract_selection_rewriter_test.go @@ -219,6 +219,83 @@ func TestInterfaceSelectionRewriter_RewriteOperation(t *testing.T) { }) } + definitionC := ` + interface Named { + name: String! + } + + type User implements Named { + id: ID! + name: String! + surname: String! + } + + type Admin implements Named { + id: ID! + name: String! + title: String! + } + + type Query { + user: Named! + }` + + upstreamDefinitionC := ` + interface Named { + name: String! + } + + type User implements Named @key(fields: "id") { + id: ID! + name: String! @requires(fields: "fullName") + fullName: String! @external + surname: String! + } + + type Admin implements Named @key(fields: "id") { + id: ID! + name: String! @requires(fields: "fullName") + fullName: String! @external + } + + type Query { + user: Named! + }` + + dsBuilderC := func() *dsBuilder { + return dsb(). + RootNode("Query", "user"). + RootNode("User", "id", "name", "surname"). + AddRootNodeExternalFieldNames("User", "fullName"). + RootNode("Admin", "id", "name"). + AddRootNodeExternalFieldNames("Admin", "fullName"). + ChildNode("Named", "name"). + WithMetadata(func(m *FederationMetaData) { + m.Requires = []FederationFieldConfiguration{ + { + TypeName: "User", + FieldName: "name", + SelectionSet: "fullName", + }, + { + TypeName: "Admin", + FieldName: "name", + SelectionSet: "fullName", + }, + } + m.Keys = []FederationFieldConfiguration{ + { + TypeName: "User", + SelectionSet: "id", + }, + { + TypeName: "Admin", + SelectionSet: "id", + }, + } + }) + } + testCases := []testCase{ { name: "should flatten interfaces for gRPC", @@ -3943,6 +4020,60 @@ func TestInterfaceSelectionRewriter_RewriteOperation(t *testing.T) { }`, shouldRewrite: true, }, + { + name: "field is an interface members of which has requires directive. query do not have fragments", + definition: definitionC, + upstreamDefinition: upstreamDefinitionC, + dsBuilder: dsBuilderC(), + fieldName: "user", + operation: ` + query { + user { + name + } + }`, + expectedOperation: ` + query { + user { + ... on Admin { + name + } + ... on User { + name + } + } + }`, + shouldRewrite: true, + }, + { + name: "field is an interface members of which has requires directive. query has concrete type fragment", + definition: definitionC, + upstreamDefinition: upstreamDefinitionC, + dsBuilder: dsBuilderC(), + fieldName: "user", + operation: ` + query { + user { + name + ... on User { + surname + } + } + }`, + expectedOperation: ` + query { + user { + ... on Admin { + name + } + ... on User { + name + surname + } + } + }`, + shouldRewrite: true, + }, } for _, testCase := range testCases {