UNPKG

@apollo/federation-internals

Version:
937 lines 97.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.removeInactiveProvidesAndRequires = exports.addSubgraphToError = exports.addSubgraphToASTNode = exports.Subgraph = exports.FEDERATION_OPERATION_FIELDS = exports.entitiesFieldName = exports.serviceFieldName = exports.FEDERATION_OPERATION_TYPES = exports.entityTypeSpec = exports.serviceTypeSpec = exports.anyTypeSpec = exports.Subgraphs = exports.subgraphsFromServiceList = exports.collectTargetFields = exports.parseFieldSetArgument = exports.newEmptyFederation2Schema = exports.buildSubgraph = exports.isInterfaceObjectType = exports.isEntityType = exports.isFederationField = exports.isFederationSubgraphSchema = exports.federationMetadata = exports.printSubgraphNames = exports.asFed2SubgraphDocument = exports.FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS_UPGRADED = exports.FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS = exports.FEDERATION2_LINK_WITH_FULL_IMPORTS = exports.setSchemaAsFed2Subgraph = exports.FederationBlueprint = exports.hasAppliedDirective = exports.isFederationDirectiveDefinedInSchema = exports.FederationMetadata = exports.collectUsedFields = exports.parseContext = exports.FEDERATION_UNNAMED_SUBGRAPH_NAME = exports.FEDERATION_RESERVED_SUBGRAPH_NAME = void 0; const definitions_1 = require("./definitions"); const utils_1 = require("./utils"); const specifiedRules_1 = require("graphql/validation/specifiedRules"); const graphql_1 = require("graphql"); const KnownTypeNamesInFederationRule_1 = require("./validation/KnownTypeNamesInFederationRule"); const buildSchema_1 = require("./buildSchema"); const operations_1 = require("./operations"); const tagSpec_1 = require("./specs/tagSpec"); const error_1 = require("./error"); const precompute_1 = require("./precompute"); const coreSpec_1 = require("./specs/coreSpec"); const federationSpec_1 = require("./specs/federationSpec"); const print_1 = require("./print"); const directiveAndTypeSpecification_1 = require("./directiveAndTypeSpecification"); const suggestions_1 = require("./suggestions"); const knownCoreFeatures_1 = require("./knownCoreFeatures"); const joinSpec_1 = require("./specs/joinSpec"); const costSpec_1 = require("./specs/costSpec"); const linkSpec = coreSpec_1.LINK_VERSIONS.latest(); const tagSpec = tagSpec_1.TAG_VERSIONS.latest(); const federationSpec = (version) => { if (!version) return federationSpec_1.FEDERATION_VERSIONS.latest(); const spec = federationSpec_1.FEDERATION_VERSIONS.find(version); (0, utils_1.assert)(spec, `Federation spec version ${version} is not known`); return spec; }; const autoExpandedFederationSpec = federationSpec(new coreSpec_1.FeatureVersion(2, 4)); const latestFederationSpec = federationSpec(); exports.FEDERATION_RESERVED_SUBGRAPH_NAME = '_'; exports.FEDERATION_UNNAMED_SUBGRAPH_NAME = '<unnamed>'; const FEDERATION_OMITTED_VALIDATION_RULES = [ graphql_1.PossibleTypeExtensionsRule, graphql_1.KnownTypeNamesRule ]; const FEDERATION_SPECIFIC_VALIDATION_RULES = [ KnownTypeNamesInFederationRule_1.KnownTypeNamesInFederationRule ]; const FEDERATION_VALIDATION_RULES = specifiedRules_1.specifiedSDLRules.filter(rule => !FEDERATION_OMITTED_VALIDATION_RULES.includes(rule)).concat(FEDERATION_SPECIFIC_VALIDATION_RULES); const ALL_DEFAULT_FEDERATION_DIRECTIVE_NAMES = Object.values(federationSpec_1.FederationDirectiveName); const FAKE_FED1_CORE_FEATURE_TO_RENAME_TYPES = new definitions_1.CoreFeature(new coreSpec_1.FeatureUrl('<fed1>', 'fed1', new coreSpec_1.FeatureVersion(0, 1)), 'fed1', new definitions_1.Directive('fed1'), federationSpec_1.FEDERATION1_TYPES.map((spec) => ({ name: spec.name, as: '_' + spec.name }))); function validateFieldSetSelections({ directiveName, selectionSet, hasExternalInParents, metadata, onError, allowOnNonExternalLeafFields, allowFieldsWithArguments, }) { for (const selection of selectionSet.selections()) { const appliedDirectives = selection.element.appliedDirectives; if (appliedDirectives.length > 0) { onError(error_1.ERROR_CATEGORIES.DIRECTIVE_IN_FIELDS_ARG.get(directiveName).err(`cannot have directive applications in the @${directiveName}(fields:) argument but found ${appliedDirectives.join(', ')}.`)); } if (selection.kind === 'FieldSelection') { const field = selection.element.definition; const isExternal = metadata.isFieldExternal(field); if (!allowFieldsWithArguments && field.hasArguments()) { onError(error_1.ERROR_CATEGORIES.FIELDS_HAS_ARGS.get(directiveName).err(`field ${field.coordinate} cannot be included because it has arguments (fields with argument are not allowed in @${directiveName})`, { nodes: field.sourceAST })); } const mustBeExternal = !selection.selectionSet && !allowOnNonExternalLeafFields && !hasExternalInParents; if (!isExternal && mustBeExternal) { const errorCode = error_1.ERROR_CATEGORIES.DIRECTIVE_FIELDS_MISSING_EXTERNAL.get(directiveName); if (metadata.isFieldFakeExternal(field)) { onError(errorCode.err(`field "${field.coordinate}" should not be part of a @${directiveName} since it is already "effectively" provided by this subgraph ` + `(while it is marked @${federationSpec_1.FederationDirectiveName.EXTERNAL}, it is a @${federationSpec_1.FederationDirectiveName.KEY} field of an extension type, which are not internally considered external for historical/backward compatibility reasons)`, { nodes: field.sourceAST })); } else { onError(errorCode.err(`field "${field.coordinate}" should not be part of a @${directiveName} since it is already provided by this subgraph (it is not marked @${federationSpec_1.FederationDirectiveName.EXTERNAL})`, { nodes: field.sourceAST })); } } if (selection.selectionSet) { let newHasExternalInParents = hasExternalInParents || isExternal; const parentType = field.parent; if (!newHasExternalInParents && (0, definitions_1.isInterfaceType)(parentType)) { for (const implem of parentType.possibleRuntimeTypes()) { const fieldInImplem = implem.field(field.name); if (fieldInImplem && metadata.isFieldExternal(fieldInImplem)) { newHasExternalInParents = true; break; } } } validateFieldSetSelections({ directiveName, selectionSet: selection.selectionSet, hasExternalInParents: newHasExternalInParents, metadata, onError, allowOnNonExternalLeafFields, allowFieldsWithArguments, }); } } else { validateFieldSetSelections({ directiveName, selectionSet: selection.selectionSet, hasExternalInParents, metadata, onError, allowOnNonExternalLeafFields, allowFieldsWithArguments, }); } } } function validateFieldSet({ type, directive, metadata, errorCollector, allowOnNonExternalLeafFields, allowFieldsWithArguments, onFields, }) { try { const fieldAccessor = onFields ? (type, fieldName) => { const field = type.field(fieldName); if (field) { onFields(field); } return field; } : undefined; const selectionSet = parseFieldSetArgument({ parentType: type, directive, fieldAccessor }); validateFieldSetSelections({ directiveName: directive.name, selectionSet, hasExternalInParents: false, metadata, onError: (error) => errorCollector.push(handleFieldSetValidationError(directive, error)), allowOnNonExternalLeafFields, allowFieldsWithArguments, }); } catch (e) { if (e instanceof graphql_1.GraphQLError) { errorCollector.push(e); } else { throw e; } } } function handleFieldSetValidationError(directive, originalError, messageUpdater) { const nodes = (0, definitions_1.sourceASTs)(directive); if (originalError.nodes) { nodes.push(...originalError.nodes); } let codeDef = (0, error_1.errorCodeDef)(originalError); if (!codeDef || codeDef === error_1.ERRORS.INVALID_GRAPHQL) { codeDef = error_1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS.get(directive.name); } let msg = originalError.message.trim(); if (messageUpdater) { msg = messageUpdater(msg); } return codeDef.err(`${fieldSetErrorDescriptor(directive)}: ${msg}`, { nodes, originalError, }); } function fieldSetErrorDescriptor(directive) { return `On ${fieldSetTargetDescription(directive)}, for ${directiveStrUsingASTIfPossible(directive)}`; } function directiveStrUsingASTIfPossible(directive) { return directive.sourceAST ? (0, graphql_1.print)(directive.sourceAST) : directive.toString(); } function fieldSetTargetDescription(directive) { var _a; const targetKind = directive.parent instanceof definitions_1.FieldDefinition ? "field" : "type"; return `${targetKind} "${(_a = directive.parent) === null || _a === void 0 ? void 0 : _a.coordinate}"`; } function parseContext(input) { const regex = /^(?:[\n\r\t ,]|#[^\n\r]*(?![^\n\r]))*\$(?:[\n\r\t ,]|#[^\n\r]*(?![^\n\r]))*([A-Za-z_]\w*(?!\w))([\s\S]*)$/; const match = input.match(regex); if (!match) { return { context: undefined, selection: undefined }; } const [, context, selection] = match; return { context, selection, }; } exports.parseContext = parseContext; const wrapResolvedType = ({ originalType, resolvedType, }) => { const stack = []; let unwrappedType = originalType; while (unwrappedType.kind === 'NonNullType' || unwrappedType.kind === 'ListType') { stack.push(unwrappedType.kind); unwrappedType = unwrappedType.ofType; } let type = resolvedType; while (stack.length > 0) { const kind = stack.pop(); if (kind === 'ListType') { type = new definitions_1.ListType(type); } } return type; }; const validateFieldValueType = ({ currentType, selectionSet, errorCollector, metadata, fromContextParent, }) => { const selections = selectionSet.selections(); const interfaceObjectDirective = metadata.interfaceObjectDirective(); if (currentType.kind === 'ObjectType' && isFederationDirectiveDefinedInSchema(interfaceObjectDirective) && (currentType.appliedDirectivesOf(interfaceObjectDirective).length > 0)) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "is used in "${fromContextParent.coordinate}" but the selection is invalid: One of the types in the selection is an interfaceObject: "${currentType.name}"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); } const typesArray = selections.map((selection) => { if (selection.kind !== 'FieldSelection') { return { resolvedType: undefined }; } const { element, selectionSet: childSelectionSet } = selection; (0, utils_1.assert)(element.definition.type, 'Element type definition should exist'); let type = element.definition.type; if (childSelectionSet) { (0, utils_1.assert)((0, definitions_1.isCompositeType)((0, definitions_1.baseType)(type)), 'Child selection sets should only exist on composite types'); const { resolvedType } = validateFieldValueType({ currentType: (0, definitions_1.baseType)(type), selectionSet: childSelectionSet, errorCollector, metadata, fromContextParent, }); if (!resolvedType) { return { resolvedType: undefined }; } return { resolvedType: wrapResolvedType({ originalType: type, resolvedType }) }; } (0, utils_1.assert)((0, definitions_1.isLeafType)((0, definitions_1.baseType)(type)), 'Expected a leaf type'); return { resolvedType: wrapResolvedType({ originalType: type, resolvedType: (0, definitions_1.baseType)(type) }) }; }); return typesArray.reduce((acc, { resolvedType }) => { var _a; if (((_a = acc.resolvedType) === null || _a === void 0 ? void 0 : _a.toString()) === (resolvedType === null || resolvedType === void 0 ? void 0 : resolvedType.toString())) { return { resolvedType }; } return { resolvedType: undefined }; }); }; const validateSelectionFormat = ({ context, selection, fromContextParent, errorCollector, }) => { try { const node = (0, operations_1.parseOperationAST)(selection.trim().startsWith('{') ? selection : `{${selection}}`); const selections = node.selectionSet.selections; if (selections.length === 0) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: no selection is made`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); return { selectionType: 'error' }; } const firstSelectionKind = selections[0].kind; if (firstSelectionKind === 'Field') { if (selections.length !== 1) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: multiple selections are made`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); return { selectionType: 'error' }; } return { selectionType: 'field' }; } else if (firstSelectionKind === 'InlineFragment') { const inlineFragmentTypeConditions = new Set(); if (!selections.every((s) => s.kind === 'InlineFragment')) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: multiple fields could be selected`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); return { selectionType: 'error' }; } selections.forEach((s) => { (0, utils_1.assert)(s.kind === 'InlineFragment', 'Expected an inline fragment'); const { typeCondition } = s; if (typeCondition) { inlineFragmentTypeConditions.add(typeCondition.name.value); } }); if (inlineFragmentTypeConditions.size !== selections.length) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: type conditions have same name`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); return { selectionType: 'error' }; } return { selectionType: 'inlineFragment', typeConditions: inlineFragmentTypeConditions, }; } else if (firstSelectionKind === 'FragmentSpread') { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: fragment spread is not allowed`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); return { selectionType: 'error' }; } else { (0, utils_1.assertUnreachable)(firstSelectionKind); } } catch (err) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: ${err.message}`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); return { selectionType: 'error' }; } }; function isValidImplementationFieldType(fieldType, implementedFieldType) { if ((0, definitions_1.isNonNullType)(fieldType)) { if ((0, definitions_1.isNonNullType)(implementedFieldType)) { return isValidImplementationFieldType(fieldType.ofType, implementedFieldType.ofType); } else { return isValidImplementationFieldType(fieldType.ofType, implementedFieldType); } } if ((0, definitions_1.isListType)(fieldType) && (0, definitions_1.isListType)(implementedFieldType)) { return isValidImplementationFieldType(fieldType.ofType, implementedFieldType.ofType); } return !(0, definitions_1.isWrapperType)(fieldType) && !(0, definitions_1.isWrapperType)(implementedFieldType) && fieldType.name === implementedFieldType.name; } function selectionSetHasDirectives(selectionSet) { return (0, operations_1.hasSelectionWithPredicate)(selectionSet, (s) => { if (s.kind === 'FieldSelection') { return s.element.appliedDirectives.length > 0; } else if (s.kind === 'FragmentSelection') { return s.element.appliedDirectives.length > 0; } else { (0, utils_1.assertUnreachable)(s); } }); } function selectionSetHasAlias(selectionSet) { return (0, operations_1.hasSelectionWithPredicate)(selectionSet, (s) => { if (s.kind === 'FieldSelection') { return s.element.alias !== undefined; } return false; }); } function validateFieldValue({ context, selection, fromContextParent, setContextLocations, errorCollector, metadata, }) { const expectedType = fromContextParent.type; (0, utils_1.assert)(expectedType, 'Expected a type'); const validateSelectionFormatResults = validateSelectionFormat({ context, selection, fromContextParent, errorCollector }); const selectionType = validateSelectionFormatResults.selectionType; if (selectionType === 'error') { return; } const usedTypeConditions = new Set; for (const location of setContextLocations) { let selectionSet; try { selectionSet = (0, operations_1.parseSelectionSet)({ parentType: location, source: selection }); } catch (e) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid for type ${location.name}. Error: ${e.message}`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); return; } if (selectionSetHasDirectives(selectionSet)) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: directives are not allowed in the selection`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); } if (selectionSetHasAlias(selectionSet)) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: aliases are not allowed in the selection`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); } if (selectionType === 'field') { const { resolvedType } = validateFieldValueType({ currentType: location, selectionSet, errorCollector, metadata, fromContextParent, }); if (resolvedType === undefined || !isValidImplementationFieldType(resolvedType, expectedType)) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: the type of the selection "${resolvedType}" does not match the expected type "${expectedType === null || expectedType === void 0 ? void 0 : expectedType.toString()}"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); return; } } else if (selectionType === 'inlineFragment') { const selections = []; for (const selection of selectionSet.selections()) { if (selection.kind !== 'FragmentSelection') { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: selection should only contain a single field or at least one inline fragment}"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); continue; } const { typeCondition } = selection.element; if (!typeCondition) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: inline fragments must have type conditions"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); continue; } if (typeCondition.kind === 'ObjectType') { if ((0, definitions_1.possibleRuntimeTypes)(location).includes(typeCondition)) { selections.push(selection); usedTypeConditions.add(typeCondition.name); } } else { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: type conditions must be an object type"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); } } if (selections.length === 0) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: no type condition matches the location "${location.coordinate}"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); return; } else { for (const selection of selections) { let { resolvedType } = validateFieldValueType({ currentType: selection.element.typeCondition, selectionSet: selection.selectionSet, errorCollector, metadata, fromContextParent, }); if (resolvedType === undefined) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: the type of the selection does not match the expected type "${expectedType === null || expectedType === void 0 ? void 0 : expectedType.toString()}"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); return; } if ((0, definitions_1.isNonNullType)(resolvedType)) { resolvedType = resolvedType.ofType; } if (!isValidImplementationFieldType(resolvedType, expectedType)) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: the type of the selection "${resolvedType === null || resolvedType === void 0 ? void 0 : resolvedType.toString()}" does not match the expected type "${expectedType === null || expectedType === void 0 ? void 0 : expectedType.toString()}"`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); return; } } } } } if (validateSelectionFormatResults.selectionType === 'inlineFragment') { for (const typeCondition of validateSelectionFormatResults.typeConditions) { if (!usedTypeConditions.has(typeCondition)) { errorCollector.push(error_1.ERRORS.CONTEXT_INVALID_SELECTION.err(`Context "${context}" is used in "${fromContextParent.coordinate}" but the selection is invalid: type condition "${typeCondition}" is never used.`, { nodes: (0, definitions_1.sourceASTs)(fromContextParent) })); } } } } function validateAllFieldSet({ definition, targetTypeExtractor, errorCollector, metadata, isOnParentType = false, allowOnNonExternalLeafFields = false, allowFieldsWithArguments = false, allowOnInterface = false, onFields, }) { for (const application of definition.applications()) { const elt = application.parent; const type = targetTypeExtractor(elt); const parentType = isOnParentType ? type : elt.parent; if ((0, definitions_1.isInterfaceType)(parentType) && !allowOnInterface) { const code = error_1.ERROR_CATEGORIES.DIRECTIVE_UNSUPPORTED_ON_INTERFACE.get(definition.name); errorCollector.push(code.err(isOnParentType ? `Cannot use ${definition.coordinate} on interface "${parentType.coordinate}": ${definition.coordinate} is not yet supported on interfaces` : `Cannot use ${definition.coordinate} on ${fieldSetTargetDescription(application)} of parent type "${parentType}": ${definition.coordinate} is not yet supported within interfaces`, { nodes: (0, definitions_1.sourceASTs)(application).concat(isOnParentType ? [] : (0, definitions_1.sourceASTs)(type)) })); } validateFieldSet({ type, directive: application, metadata, errorCollector, allowOnNonExternalLeafFields, allowFieldsWithArguments, onFields, }); } } function collectUsedFields(metadata) { const usedFields = new Set(); collectUsedFieldsForDirective(metadata.keyDirective(), type => type, usedFields); collectUsedFieldsForDirective(metadata.requiresDirective(), field => field.parent, usedFields); collectUsedFieldsForDirective(metadata.providesDirective(), field => { const type = (0, definitions_1.baseType)(field.type); return (0, definitions_1.isCompositeType)(type) ? type : undefined; }, usedFields); collectUsedFieldsForFromContext(metadata, usedFields); for (const itfType of metadata.schema.interfaceTypes()) { const runtimeTypes = itfType.possibleRuntimeTypes(); for (const field of itfType.fields()) { for (const runtimeType of runtimeTypes) { const implemField = runtimeType.field(field.name); if (implemField) { usedFields.add(implemField); } } } } return usedFields; } exports.collectUsedFields = collectUsedFields; function collectUsedFieldsForFromContext(metadata, usedFieldDefs) { const fromContextDirective = metadata.fromContextDirective(); const contextDirective = metadata.contextDirective(); if (!isFederationDirectiveDefinedInSchema(fromContextDirective) || !isFederationDirectiveDefinedInSchema(contextDirective)) { return; } const entryPoints = new Map(); for (const application of contextDirective.applications()) { const type = application.parent; if (!type) { continue; } const context = application.arguments().name; if (!entryPoints.has(context)) { entryPoints.set(context, new Set()); } entryPoints.get(context).add(type); } for (const application of fromContextDirective.applications()) { const type = application.parent; if (!type) { continue; } const fieldValue = application.arguments().field; const { context, selection } = parseContext(fieldValue); if (!context) { continue; } const contextTypes = entryPoints.get(context); if (!contextTypes) { continue; } for (const contextType of contextTypes) { try { const fieldAccessor = (t, f) => { const field = t.field(f); if (field) { usedFieldDefs.add(field); if ((0, definitions_1.isInterfaceType)(t)) { for (const implType of t.possibleRuntimeTypes()) { const implField = implType.field(f); if (implField) { usedFieldDefs.add(implField); } } } } return field; }; (0, operations_1.parseSelectionSet)({ parentType: contextType, source: selection, fieldAccessor }); } catch (e) { } } } } function collectUsedFieldsForDirective(definition, targetTypeExtractor, usedFieldDefs) { for (const application of definition.applications()) { const type = targetTypeExtractor(application.parent); if (!type) { continue; } collectTargetFields({ parentType: type, directive: application, includeInterfaceFieldsImplementations: true, validate: false, }).forEach((field) => usedFieldDefs.add(field)); } } function validateAllExternalFieldsUsed(metadata, errorCollector) { for (const type of metadata.schema.types()) { if (!(0, definitions_1.isObjectType)(type) && !(0, definitions_1.isInterfaceType)(type)) { continue; } for (const field of type.fields()) { if (!metadata.isFieldExternal(field) || metadata.isFieldUsed(field)) { continue; } errorCollector.push(error_1.ERRORS.EXTERNAL_UNUSED.err(`Field "${field.coordinate}" is marked @external but is not used in any federation directive (@key, @provides, @requires) or to satisfy an interface;` + ' the field declaration has no use and should be removed (or the field should not be @external).', { nodes: field.sourceAST })); } } } function validateNoExternalOnInterfaceFields(metadata, errorCollector) { for (const itf of metadata.schema.interfaceTypes()) { for (const field of itf.fields()) { if (metadata.isFieldExternal(field)) { errorCollector.push(error_1.ERRORS.EXTERNAL_ON_INTERFACE.err(`Interface type field "${field.coordinate}" is marked @external but @external is not allowed on interface fields (it is nonsensical).`, { nodes: field.sourceAST })); } } } } function validateKeyOnInterfacesAreAlsoOnAllImplementations(metadata, errorCollector) { for (const itfType of metadata.schema.interfaceTypes()) { const implementations = itfType.possibleRuntimeTypes(); for (const keyApplication of itfType.appliedDirectivesOf(metadata.keyDirective())) { const fields = parseFieldSetArgument({ parentType: itfType, directive: keyApplication, validate: false }); const isResolvable = !(keyApplication.arguments().resolvable === false); const implementationsWithKeyButNotResolvable = new Array(); const implementationsMissingKey = new Array(); for (const type of implementations) { const matchingApp = type.appliedDirectivesOf(metadata.keyDirective()).find((app) => { const appFields = parseFieldSetArgument({ parentType: type, directive: app, validate: false }); return fields.equals(appFields); }); if (matchingApp) { if (isResolvable && matchingApp.arguments().resolvable === false) { implementationsWithKeyButNotResolvable.push(type); } } else { implementationsMissingKey.push(type); } } if (implementationsMissingKey.length > 0) { const typesString = (0, utils_1.printHumanReadableList)(implementationsMissingKey.map((i) => `"${i.coordinate}"`), { prefix: 'type', prefixPlural: 'types', }); errorCollector.push(error_1.ERRORS.INTERFACE_KEY_NOT_ON_IMPLEMENTATION.err(`Key ${keyApplication} on interface type "${itfType.coordinate}" is missing on implementation ${typesString}.`, { nodes: (0, definitions_1.sourceASTs)(...implementationsMissingKey) })); } else if (implementationsWithKeyButNotResolvable.length > 0) { const typesString = (0, utils_1.printHumanReadableList)(implementationsWithKeyButNotResolvable.map((i) => `"${i.coordinate}"`), { prefix: 'type', prefixPlural: 'types', }); errorCollector.push(error_1.ERRORS.INTERFACE_KEY_NOT_ON_IMPLEMENTATION.err(`Key ${keyApplication} on interface type "${itfType.coordinate}" should be resolvable on all implementation types, but is declared with argument "@key(resolvable:)" set to false in ${typesString}.`, { nodes: (0, definitions_1.sourceASTs)(...implementationsWithKeyButNotResolvable) })); } } } } function validateInterfaceObjectsAreOnEntities(metadata, errorCollector) { for (const application of metadata.interfaceObjectDirective().applications()) { if (!isEntityType(application.parent)) { errorCollector.push(error_1.ERRORS.INTERFACE_OBJECT_USAGE_ERROR.err(`The @interfaceObject directive can only be applied to entity types but type "${application.parent.coordinate}" has no @key in this subgraph.`, { nodes: application.parent.sourceAST })); } } } function validateShareableNotRepeatedOnSameDeclaration(element, metadata, errorCollector) { const shareableApplications = element.appliedDirectivesOf(metadata.shareableDirective()); if (shareableApplications.length <= 1) { return; } const byExtensions = shareableApplications.reduce((acc, v) => { const ext = v.ofExtension(); if (ext) { acc.with.add(ext, v); } else { acc.without.push(v); } return acc; }, { without: [], with: new utils_1.MultiMap() }); const groups = [byExtensions.without].concat((0, utils_1.mapValues)(byExtensions.with)); for (const group of groups) { if (group.length > 1) { const eltStr = element.kind === 'ObjectType' ? `the same type declaration of "${element.coordinate}"` : `field "${element.coordinate}"`; errorCollector.push(error_1.ERRORS.INVALID_SHAREABLE_USAGE.err(`Invalid duplicate application of @shareable on ${eltStr}: ` + '@shareable is only repeatable on types so it can be used simultaneously on a type definition and its extensions, but it should not be duplicated on the same definition/extension declaration', { nodes: (0, definitions_1.sourceASTs)(...group) })); } } } function validateCostNotAppliedToInterface(application, errorCollector) { const parent = application.parent; if (parent instanceof definitions_1.FieldDefinition && parent.parent instanceof definitions_1.InterfaceType) { errorCollector.push(error_1.ERRORS.COST_APPLIED_TO_INTERFACE_FIELD.err(`@cost cannot be applied to interface "${parent.coordinate}"`, { nodes: (0, definitions_1.sourceASTs)(application, parent) })); } } function validateListSizeAppliedToList(application, parent, errorCollector) { const { sizedFields = [] } = application.arguments(); if (!sizedFields.length && parent.type && !(0, definitions_1.isListType)(parent.type)) { errorCollector.push(error_1.ERRORS.LIST_SIZE_APPLIED_TO_NON_LIST.err(`"${parent.coordinate}" is not a list`, { nodes: (0, definitions_1.sourceASTs)(application, parent) })); } } function validateAssumedSizeNotNegative(application, parent, errorCollector) { const { assumedSize } = application.arguments(); if (assumedSize !== undefined && assumedSize !== null && assumedSize < 0) { errorCollector.push(error_1.ERRORS.LIST_SIZE_INVALID_ASSUMED_SIZE.err(`Assumed size of "${parent.coordinate}" cannot be negative`, { nodes: (0, definitions_1.sourceASTs)(application, parent) })); } } function isNonNullIntType(ty) { return (0, definitions_1.isNonNullType)(ty) && (0, definitions_1.isIntType)(ty.ofType); } function validateSlicingArgumentsAreValidIntegers(application, parent, errorCollector) { const { slicingArguments = [] } = application.arguments(); for (const slicingArgumentName of slicingArguments) { const slicingArgument = parent.argument(slicingArgumentName); if (!(slicingArgument === null || slicingArgument === void 0 ? void 0 : slicingArgument.type)) { errorCollector.push(error_1.ERRORS.LIST_SIZE_INVALID_SLICING_ARGUMENT.err(`Slicing argument "${slicingArgumentName}" is not an argument of "${parent.coordinate}"`, { nodes: (0, definitions_1.sourceASTs)(application, parent) })); } else if (!(0, definitions_1.isIntType)(slicingArgument.type) && !isNonNullIntType(slicingArgument.type)) { errorCollector.push(error_1.ERRORS.LIST_SIZE_INVALID_SLICING_ARGUMENT.err(`Slicing argument "${slicingArgument.coordinate}" must be Int or Int!`, { nodes: (0, definitions_1.sourceASTs)(application, parent) })); } } } function isNonNullListType(ty) { return (0, definitions_1.isNonNullType)(ty) && (0, definitions_1.isListType)(ty.ofType); } function validateSizedFieldsAreValidLists(application, parent, errorCollector) { const { sizedFields = [] } = application.arguments(); if (sizedFields.length) { if (!parent.type || !(0, definitions_1.isCompositeType)(parent.type)) { errorCollector.push(error_1.ERRORS.LIST_SIZE_INVALID_SIZED_FIELD.err(`Sized fields cannot be used because "${parent.type}" is not a composite type`, { nodes: (0, definitions_1.sourceASTs)(application, parent) })); } else { for (const sizedFieldName of sizedFields) { const sizedField = parent.type.field(sizedFieldName); if (!sizedField) { errorCollector.push(error_1.ERRORS.LIST_SIZE_INVALID_SIZED_FIELD.err(`Sized field "${sizedFieldName}" is not a field on type "${parent.type.coordinate}"`, { nodes: (0, definitions_1.sourceASTs)(application, parent) })); } else if (!sizedField.type || !((0, definitions_1.isListType)(sizedField.type) || isNonNullListType(sizedField.type))) { errorCollector.push(error_1.ERRORS.LIST_SIZE_APPLIED_TO_NON_LIST.err(`Sized field "${sizedField.coordinate}" is not a list`, { nodes: (0, definitions_1.sourceASTs)(application, parent) })); } } } } } class FederationMetadata { constructor(schema) { this.schema = schema; } onInvalidate() { this._externalTester = undefined; this._sharingPredicate = undefined; this._isFed2Schema = undefined; this._fieldUsedPredicate = undefined; } isFed2Schema() { if (!this._isFed2Schema) { const feature = this.federationFeature(); this._isFed2Schema = !!feature && feature.url.version.satisfies(new coreSpec_1.FeatureVersion(2, 0)); } return this._isFed2Schema; } federationFeature() { var _a; return (_a = this.schema.coreFeatures) === null || _a === void 0 ? void 0 : _a.getByIdentity(latestFederationSpec.identity); } externalTester() { if (!this._externalTester) { this._externalTester = new ExternalTester(this.schema, this.isFed2Schema()); } return this._externalTester; } sharingPredicate() { if (!this._sharingPredicate) { this._sharingPredicate = (0, precompute_1.computeShareables)(this.schema); } return this._sharingPredicate; } fieldUsedPredicate() { if (!this._fieldUsedPredicate) { const usedFields = collectUsedFields(this); this._fieldUsedPredicate = (field) => !!usedFields.has(field); } return this._fieldUsedPredicate; } isFieldUsed(field) { return this.fieldUsedPredicate()(field); } isFieldExternal(field) { return this.externalTester().isExternal(field); } isFieldPartiallyExternal(field) { return this.externalTester().isPartiallyExternal(field); } isFieldFullyExternal(field) { return this.externalTester().isFullyExternal(field); } isFieldFakeExternal(field) { return this.externalTester().isFakeExternal(field); } selectionSelectsAnyExternalField(selectionSet) { return this.externalTester().selectsAnyExternalField(selectionSet); } isFieldShareable(field) { return this.sharingPredicate()(field); } isInterfaceObjectType(type) { return (0, definitions_1.isObjectType)(type) && hasAppliedDirective(type, this.interfaceObjectDirective()); } federationDirectiveNameInSchema(name) { if (this.isFed2Schema()) { const coreFeatures = this.schema.coreFeatures; (0, utils_1.assert)(coreFeatures, 'Schema should be a core schema'); const federationFeature = coreFeatures.getByIdentity(latestFederationSpec.identity); (0, utils_1.assert)(federationFeature, 'Schema should have the federation feature'); return federationFeature.directiveNameInSchema(name); } else { return name; } } federationTypeNameInSchema(name) { if (name.charAt(0) === '_') { return name; } if (this.isFed2Schema()) { const coreFeatures = this.schema.coreFeatures; (0, utils_1.assert)(coreFeatures, 'Schema should be a core schema'); const federationFeature = coreFeatures.getByIdentity(latestFederationSpec.identity); (0, utils_1.assert)(federationFeature, 'Schema should have the federation feature'); return federationFeature.typeNameInSchema(name); } else { return '_' + name; } } getLegacyFederationDirective(name) { const directive = this.getFederationDirective(name); (0, utils_1.assert)(directive, `The provided schema does not have federation directive @${name}`); return directive; } getFederationDirective(name) { return this.schema.directive(this.federationDirectiveNameInSchema(name)); } getPost20FederationDirective(name) { var _a; return (_a = this.getFederationDirective(name)) !== null && _a !== void 0 ? _a : { name, applications: () => new Set(), }; } keyDirective() { return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.KEY); } overrideDirective() { return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.OVERRIDE); } extendsDirective() { return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.EXTENDS); } externalDirective() { return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.EXTERNAL); } requiresDirective() { return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.REQUIRES); } providesDirective() { return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.PROVIDES); } shareableDirective() { return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.SHAREABLE); } tagDirective() { return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.TAG); } composeDirective() { return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.COMPOSE_DIRECTIVE); } inaccessibleDirective() { return this.getLegacyFederationDirective(federationSpec_1.FederationDirectiveName.INACCESSIBLE); } interfaceObjectDirective() { return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.INTERFACE_OBJECT); } authenticatedDirective() { return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.AUTHENTICATED); } requiresScopesDirective() { return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.REQUIRES_SCOPES); } policyDirective() { return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.POLICY); } fromContextDirective() { return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.FROM_CONTEXT); } contextDirective() { return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.CONTEXT); } costDirective() { return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.COST); } listSizeDirective() { return this.getPost20FederationDirective(federationSpec_1.FederationDirectiveName.LIST_SIZE); } allFederationDirectives() { const baseDirectives = [ this.keyDirective(), this.externalDirective(), this.requiresDirective(), this.providesDirective(), this.tagDirective(), this.extendsDirective(), ]; if (!this.isFed2Schema()) { return baseDirectives; } baseDirectives.push(this.shareableDirective()); baseDirectives.push(this.inaccessibleDirective()); baseDirectives.push(this.overrideDirective()); const composeDirective = this.composeDirective(); if (isFederationDirectiveDefinedInSchema(composeDirective)) { baseDirectives.push(composeDirective); } const interfaceObjectDirective = this.interfaceObjectDirective(); if (isFederationDirectiveDefinedInSchema(interfaceObjectDirective)) { baseDirectives.push(interfaceObjectDirective); } const authenticatedDirective = this.authenticatedDirective(); if (isFederationDirectiveDefinedInSchema(authenticatedDirective)) { baseDirectives.push(authenticatedDirective); } const requiresScopesDirective = this.requiresScopesDirective(); if (isFederationDirectiveDefinedInSchema(requiresScopesDirective)) { baseDirectives.push(requiresScopesDirective); } const policyDirective = this.policyDirective(); if (isFederationDirectiveDefinedInSchema(policyDirective)) { baseDirectives.push(policyDirective); } const contextDirective = this.contextDirective(); if (isFederationDirectiveDefinedInSchema(contextDirective)) { baseDirectives.push(contextDirective); } const fromContextDirective = this.fromContextDirective(); if (isFederationDirectiveDefinedInSchema(fromContextDirective)) { baseDirectives.push(fromContextDirective); } const costDirective = this.costDirective(); if (isFederationDirectiveDefinedInSchema(costDirective)) { baseDirectives.push(costDirective); } const listSizeDirective = this.listSizeDirective(); if (isFederationDirectiveDefinedInSchema(listSizeDirective)) { baseDirectives.push(listSizeDirective); } return baseDirectives; } entityType() { return this.schema.type(this.federationTypeNameInSchema(exports.entityTypeSpec.name)); } anyType() { return this.schema.type(this.federationTypeNameInSchema(exports.anyTypeSpec.name)); } serviceType() { return this.schema.type(this.federationTypeNameInSchema(exports.serviceTypeSpec.name)); } fieldSetType() { return this.schema.type(this.federationTypeNameInSchema(federationSpec_1.FederationTypeName.FIELD_SET)); } allFederationTypes() { const fedTypes = [ this.anyType(), this.serviceType(), ]; const fedFeature = this.federationFeature(); if (fedFeature) { const featureDef = federationSpec_1.FEDERATION_VERSIONS.find(fedFeature.url.version); (0, utils_1.assert)(featureDef, () => `Federation spec should be known, but got ${fedFeature.url}`); for (const typeSpec of featureDef.typeSpecs()) { const type = this.schema.type(fedFeature.typeNameInSchema(typeSpec.name)); if (type) { fedTypes.push(type); } } } else { fedTypes.push(this.fieldSetType()); } const entityType = this.entityType(); if (entityType) { fedTypes.push(entityType); } return fedTypes; } } exports.FederationMetadata = FederationMetadata; function isFederationDirectiveDefinedInS