diff --git a/packages/fusion/composition/src/compose.ts b/packages/fusion/composition/src/compose.ts index 70c3cb35c543..c4a06e51f912 100644 --- a/packages/fusion/composition/src/compose.ts +++ b/packages/fusion/composition/src/compose.ts @@ -2,7 +2,9 @@ import type { GraphQLArgumentConfig, GraphQLFieldConfig, GraphQLFieldConfigArgumentMap, + GraphQLList, GraphQLNamedType, + GraphQLOutputType, } from 'graphql'; import { concatAST, @@ -24,6 +26,7 @@ import { MapperKind, mapSchema, type Constructor, + type DirectableObject, type FieldMapper, } from '@graphql-tools/utils'; import type { ServiceDefinition } from '@theguild/federation-composition'; @@ -332,12 +335,46 @@ export function getAnnotatedSubgraphs( const fieldMapper: FieldMapper = (fieldConfig, fieldName) => { const fieldDirectives = getDirectiveExtensions(fieldConfig); const sourceDirectives = fieldDirectives?.source; + const typeNameSourceDirectives = getDirectiveExtensions( + ((fieldConfig.type as GraphQLList).ofType || + fieldConfig.type) as DirectableObject, + )?.source; + // Find the @source directive that has the type's name by ignoring the one that might be present for type translation. + const typeNameSourceDirectiveName = typeNameSourceDirectives?.find( + sourceDirective => !sourceDirective.type, + )?.name; + if (sourceDirectives?.length) { - const filteredSourceDirectives = sourceDirectives.filter( - sourceDirective => + const filteredSourceDirectives = sourceDirectives.filter(sourceDirective => { + // @source directive is redundant if fieldName and source type are same as in the field definition. + return ( sourceDirective.name !== fieldName || - sourceDirective.type !== fieldConfig.type.toString(), - ); + // If the field name is same as the @source directive name, we need to make sure the type value is mapped to the existing + // schema defined name or should map to the type's @source directive name. + (sourceDirective.type !== fieldConfig.type.toString() && + sourceDirective.type === typeNameSourceDirectiveName) + ); + }); + + // For each type @source directive, make sure the type is mapped to the correct schema type. + // If not, set it to a reasonable value. + for (const filteredSourceDirective of filteredSourceDirectives) { + if (!filteredSourceDirective.type) { + continue; + } + const isTypeMapped = + filteredSourceDirective.type === fieldConfig.type?.toString() || + // The type's @source directive name will not have list or non-null representation. + filteredSourceDirective.type.search( + new RegExp(`^\\[?${typeNameSourceDirectiveName}!?\\]?!?$`), + ) > -1; + + if (!isTypeMapped) { + filteredSourceDirective.type = fieldConfig.type + ?.toString() + .replace(/^(\[)?(.*)(!?\]!?|!)$/, `$1${typeNameSourceDirectiveName || '$2'}$3`); + } + } fieldDirectives.source = filteredSourceDirectives; } if ('args' in fieldConfig) {