UNPKG

@apollo/federation

Version:
120 lines (109 loc) 4.66 kB
import { GraphQLError, isObjectType, FieldNode, isListType, isInterfaceType, isNonNullType, getNullableType, isUnionType, InputValueDefinitionNode, FieldDefinitionNode, } from 'graphql'; import { logServiceAndType, errorWithCode, getFederationMetadata, findTypeNodeInServiceList, findSelectionSetOnNode, printFieldSet } from '../../utils'; import { PostCompositionValidator } from '.'; /** * - The fields argument can not have root fields that result in a list * - The fields argument can not have root fields that result in an interface * - The fields argument can not have root fields that result in a union type */ export const providesFieldsSelectInvalidType: PostCompositionValidator = ({ schema, serviceList, }) => { const errors: GraphQLError[] = []; const types = schema.getTypeMap(); for (const [typeName, namedType] of Object.entries(types)) { if (!isObjectType(namedType)) continue; // for each field, if there's a provides on it, check the type of the field // it references 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 @provides federation data, since // the only case where serviceName wouldn't exist is on a base type, and in that case, // the `provides` 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; const fieldType = field.type; if (!isObjectType(fieldType)) continue; const allFields = fieldType.getFields(); if (fieldFederationMetadata?.provides) { const selections = fieldFederationMetadata.provides as FieldNode[]; for (const selection of selections) { const name = selection.name.value; const matchingField = allFields[name]; 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, 'provides', printFieldSet(selections)); if (!matchingField) { errors.push( errorWithCode( 'PROVIDES_FIELDS_SELECT_INVALID_TYPE', logServiceAndType(serviceName, typeName, fieldName) + `A @provides selects ${name}, but ${fieldType.name}.${name} could not be found`, selectionSetNode, ), ); continue; } if ( isListType(matchingField.type) || (isNonNullType(matchingField.type) && isListType(getNullableType(matchingField.type))) ) { errors.push( errorWithCode( 'PROVIDES_FIELDS_SELECT_INVALID_TYPE', logServiceAndType(serviceName, typeName, fieldName) + `A @provides selects ${fieldType.name}.${name}, which is a list type. A field cannot @provide lists.`, selectionSetNode, ), ); } if ( isInterfaceType(matchingField.type) || (isNonNullType(matchingField.type) && isInterfaceType(getNullableType(matchingField.type))) ) { errors.push( errorWithCode( 'PROVIDES_FIELDS_SELECT_INVALID_TYPE', logServiceAndType(serviceName, typeName, fieldName) + `A @provides selects ${fieldType.name}.${name}, which is an interface type. A field cannot @provide interfaces.`, selectionSetNode, ), ); } if ( isUnionType(matchingField.type) || (isNonNullType(matchingField.type) && isUnionType(getNullableType(matchingField.type))) ) { errors.push( errorWithCode( 'PROVIDES_FIELDS_SELECT_INVALID_TYPE', logServiceAndType(serviceName, typeName, fieldName) + `A @provides selects ${fieldType.name}.${name}, which is a union type. A field cannot @provide union types.`, selectionSetNode, ), ); } } } } } return errors; };