UNPKG

@apollo/federation

Version:
94 lines (83 loc) 3.57 kB
import { isObjectType, GraphQLError, isListType, isNonNullType, FieldDefinitionNode, InputValueDefinitionNode, } from 'graphql'; import { logServiceAndType, errorWithCode, getFederationMetadata, findTypeNodeInServiceList, findDirectivesOnNode } from '../../utils'; import { PostCompositionValidator } from '.'; /** * Provides directive can only be added to return types that are entities */ export const providesNotOnEntity: 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 provides on it, check that the containing // type has a `key` field under the federation metadata. 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 && fieldFederationMetadata?.provides && !fieldFederationMetadata?.belongsToValueType ) throw Error( 'Internal Consistency Error: field with provides information does not have service name.', ); if (!serviceName) continue; const getBaseType = (type: any): any => isListType(type) || isNonNullType(type) ? getBaseType(type.ofType) : type; const baseType = getBaseType(field.type); // field has a @provides directive on it if (fieldFederationMetadata?.provides) { 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 providesDirectiveNode = findDirectivesOnNode(fieldNode, 'provides'); if (!isObjectType(baseType)) { errors.push( errorWithCode( 'PROVIDES_NOT_ON_ENTITY', logServiceAndType(serviceName, typeName, fieldName) + `uses the @provides directive but \`${typeName}.${fieldName}\` returns \`${field.type}\`, which is not an Object or List type. @provides can only be used on Object types with at least one @key, or Lists of such Objects.`, providesDirectiveNode, ), ); continue; } const fieldType = types[baseType.name]; const selectedFieldIsEntity = getFederationMetadata(fieldType)?.keys; if (!selectedFieldIsEntity) { errors.push( errorWithCode( 'PROVIDES_NOT_ON_ENTITY', logServiceAndType(serviceName, typeName, fieldName) + `uses the @provides directive but \`${typeName}.${fieldName}\` does not return a type that has a @key. Try adding a @key to the \`${baseType}\` type.`, providesDirectiveNode, ), ); } } } } return errors; };