UNPKG

@apollo/federation

Version:
69 lines (61 loc) 3.16 kB
import { isObjectType, FieldNode, GraphQLError, FieldDefinitionNode, InputValueDefinitionNode } from 'graphql'; import { logServiceAndType, errorWithCode, getFederationMetadata, findTypeNodeInServiceList, findSelectionSetOnNode, printFieldSet } from '../../utils'; import { PostCompositionValidator } from '.'; /** * for every @requires, there should be a matching @external */ export const requiresFieldsMissingExternal: PostCompositionValidator = ({ schema, serviceList, }) => { const errors: GraphQLError[] = []; const types = schema.getTypeMap(); for (const [typeName, namedType] of Object.entries(types)) { // Only object types have fields if (!isObjectType(namedType)) continue; // for each field, if there's a requires on it, check that there's a matching // @external field, and that the types referenced are from the base type for (const [fieldName, field] of Object.entries(namedType.getFields())) { const fieldFederationMetadata = getFederationMetadata(field); const serviceName = fieldFederationMetadata?.serviceName; // serviceName should always exist on fields that have @requires federation data, since // the only case where serviceName wouldn't exist is on a base type, and in that case, // the `requires` metadata should never get added to begin with. This should be caught in // composition work. This kind of error should be validated _before_ composition. if (!serviceName) continue; if (fieldFederationMetadata?.requires) { const typeFederationMetadata = getFederationMetadata(namedType); const externalFieldsOnTypeForService = typeFederationMetadata?.externals?.[serviceName]; const selections = fieldFederationMetadata?.requires as FieldNode[]; for (const selection of selections) { const foundMatchingExternal = externalFieldsOnTypeForService ? externalFieldsOnTypeForService.some( ext => ext.field.name.value === selection.name.value, ) : undefined; if (!foundMatchingExternal) { const typeNode = findTypeNodeInServiceList(typeName, serviceName, serviceList); const fieldNode = typeNode && 'fields' in typeNode ? (typeNode.fields as (FieldDefinitionNode | InputValueDefinitionNode)[])?. find(field => field.name.value === fieldName) : undefined; const selectionSetNode = findSelectionSetOnNode(fieldNode, 'requires', printFieldSet(selections)); errors.push( errorWithCode( 'REQUIRES_FIELDS_MISSING_EXTERNAL', logServiceAndType(serviceName, typeName, fieldName) + `requires the field \`${selection.name.value}\` to be marked as @external.`, // TODO (Issue #705): when we can associate locations to service name's this should be the node of the // field on the other service that needs to be marked external selectionSetNode, ), ); } } } } } return errors; };