UNPKG

@apollo/federation-internals

Version:
1,953 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 isFederationDirectiveDefinedInSchema(definition) {
    return definition instanceof definitions_1.DirectiveDefinition;
}
exports.isFederationDirectiveDefinedInSchema = isFederationDirectiveDefinedInSchema;
function hasAppliedDirective(type, definition) {
    return isFederationDirectiveDefinedInSchema(definition) && type.hasAppliedDirective(definition);
}
exports.hasAppliedDirective = hasAppliedDirective;
class FederationBlueprint extends definitions_1.SchemaBlueprint {
    constructor(withRootTypeRenaming) {
        super();
        this.withRootTypeRenaming = withRootTypeRenaming;
    }
    onAddedCoreFeature(schema, feature) {
        super.onAddedCoreFeature(schema, feature);
        if (feature.url.identity === federationSpec_1.federationIdentity) {
            const spec = federationSpec_1.FEDERATION_VERSIONS.find(feature.url.version);
            if (spec) {
                spec.addElementsToSchema(schema);
            }
        }
    }
    onMissingDirectiveDefinition(schema, directive) {
        if (directive.name === coreSpec_1.linkDirectiveDefaultName) {
            const args = directive.arguments();
            const url = args && args['url'];
            let as = undefined;
            let imports = [];
            if (url && url.startsWith(linkSpec.identity)) {
                as = args['as'];
                imports = (0, coreSpec_1.extractCoreFeatureImports)(linkSpec.url, directive);
            }
            const errors = linkSpec.addDefinitionsToSchema(schema, as, imports);
            return errors.length > 0 ? errors : schema.directive(directive.name);
        }
        return super.onMissingDirectiveDefinition(schema, directive);
    }
    ignoreParsedField(type, fieldName) {
        if (!exports.FEDERATION_OPERATION_FIELDS.includes(fieldName)) {
            return false;
        }
        const metadata = federationMetadata(type.schema());
        return !!metadata && !metadata.isFed2Schema();
    }
    onConstructed(schema) {
        const existing = federationMetadata(schema);
        if (!existing) {
            schema['_federationMetadata'] = new FederationMetadata(schema);
        }
    }
    onDirectiveDefinitionAndSchemaParsed(schema) {
        const errors = completeSubgraphSchema(schema);
        schema.schemaDefinition.processUnappliedDirectives();
        return errors;
    }
    onInvalidation(schema) {
        super.onInvalidation(schema);
        const metadata = federationMetadata(schema);
        (0, utils_1.assert)(metadata, 'Federation schema should have had its metadata set on construction');
        FederationMetadata.prototype['onInvalidate'].call(metadata);
    }
    onValidation(schema) {
        var _a, _b, _c, _d, _e, _f;
        const errorCollector = super.onValidation(schema);
        if (this.withRootTypeRenaming) {
            for (const k of definitions_1.allSchemaRootKinds) {
                const type = (_a = schema.schemaDefinition.root(k)) === null || _a === void 0 ? void 0 : _a.type;
                const defaultName = (0, definitions_1.defaultRootName)(k);
                if (type && type.name !== defaultName) {
                    const existing = schema.type(defaultName);
                    if (existing) {
                        errorCollector.push(error_1.ERROR_CATEGORIES.ROOT_TYPE_USED.get(k).err(`The schema has a type named "${defaultName}" but it is not set as the ${k} root type ("${type.name}" is instead): `
                            + 'this is not supported by federation. '
                            + 'If a root type does not use its default name, there should be no other type with that default name.', { nodes: (0, definitions_1.sourceASTs)(type, existing) }));
                    }
                    type.rename(defaultName);
                }
            }
        }
        const metadata = federationMetadata(schema);
        (0, utils_1.assert)(metadata, 'Federation schema should have had its metadata set on construction');
        if (!metadata.isFed2Schema()) {
            return errorCollector;
        }
        const keyDirective = metadata.keyDirective();
        validateAllFieldSet({
            definition: keyDirective,
            targetTypeExtractor: type => type,
            errorCollector,
            metadata,
            isOnParentType: true,
            allowOnNonExternalLeafFields: true,
            allowOnInterface: metadata.federationFeature().url.version.compareTo(new coreSpec_1.FeatureVersion(2, 3)) >= 0,
            onFields: field => {
                const type = (0, definitions_1.baseType)(field.type);
                if ((0, definitions_1.isUnionType)(type) || (0, definitions_1.isInterfaceType)(type)) {
                    let kind = type.kind;
                    kind = kind.slice(0, kind.length - 'Type'.length);
                    throw error_1.ERRORS.KEY_FIELDS_SELECT_INVALID_TYPE.err(`field "${field.coordinate}" is a ${kind} type which is not allowed in @key`);
                }
            }
        });
        validateAllFieldSet({
            definition: metadata.requiresDirective(),
            targetTypeExtractor: field => field.parent,
            errorCollector,
            metadata,
            allowFieldsWithArguments: true,
        });
        validateAllFieldSet({
            definition: metadata.providesDirective(),
            targetTypeExtractor: field => {
                if (metadata.isFieldExternal(field)) {
                    throw error_1.ERRORS.EXTERNAL_COLLISION_WITH_ANOTHER_DIRECTIVE.err(`Cannot have both @provides and @external on field "${field.coordinate}"`, { nodes: field.sourceAST });
                }
                const type = (0, definitions_1.baseType)(field.type);
                if (!(0, definitions_1.isCompositeType)(type)) {
                    throw error_1.ERRORS.PROVIDES_ON_NON_OBJECT_FIELD.err(`Invalid @provides directive on field "${field.coordinate}": field has type "${field.type}" which is not a Composite Type`, { nodes: field.sourceAST });
                }
                return type;
            },
            errorCollector,
            metadata,
        });
        const contextDirective = metadata.contextDirective();
        const contextToTypeMap = new Map();
        for (const application of contextDirective.applications()) {
            const parent = application.parent;
            const name = application.arguments().name;
            const match = name.match(/^([A-Za-z]\w*)$/);
            if (name.includes('_')) {
                errorCollector.push(error_1.ERRORS.CONTEXT_NAME_INVALID.err(`Context name "${name}" may not contain an underscore.`, { nodes: (0, definitions_1.sourceASTs)(application) }));
            }
            else if (!match) {
                errorCollector.push(error_1.ERRORS.CONTEXT_NAME_INVALID.err(`Context name "${name}" is invalid. It should have only alphanumeric characters.`, { nodes: (0, definitions_1.sourceASTs)(application) }));
            }
            const types = contextToTypeMap.get(name);
            if (types) {
                types.push(parent);
            }
            else {
                contextToTypeMap.set(name, [parent]);
            }
        }
        const fromContextDirective = metadata.fromContextDirective();
        for (const application of fromContextDirective.applications()) {
            const { field } = application.arguments();
            const { context, selection } = parseContext(field);
            if (application.parent.parent.kind === 'DirectiveDefinition') {
                errorCollector.push(error_1.ERRORS.CONTEXT_NOT_SET.err(`@fromContext argument cannot be used on a directive definition "${application.parent.coordinate}".`, { nodes: (0, definitions_1.sourceASTs)(application) }));
                continue;
            }
            const parent = application.parent;
            if (((_c = (_b = parent === null || parent === void 0 ? void 0 : parent.parent) === null || _b === void 0 ? void 0 : _b.parent) === null || _c === void 0 ? void 0 : _c.kind) !== 'ObjectType') {
                errorCollector.push(error_1.ERRORS.CONTEXT_NOT_SET.err(`@fromContext argument cannot be used on a field that exists on an abstract type "${application.parent.coordinate}".`, { nodes: (0, definitions_1.sourceASTs)(application) }));
                continue;
            }
            const objectType = parent.parent.parent;
            for (const implementedInterfaceType of objectType.interfaces()) {
                const implementedInterfaceField = implementedInterfaceType.field(parent.parent.name);
                if (implementedInterfaceField) {
                    errorCollector.push(error_1.ERRORS.CONTEXT_NOT_SET.err(`@fromContext argument cannot be used on a field implementing an interface field "${implementedInterfaceField.coordinate}".`, { nodes: (0, definitions_1.sourceASTs)(application) }));
                }
            }
            if (parent.defaultValue !== undefined) {
                errorCollector.push(error_1.ERRORS.CONTEXT_NOT_SET.err(`@fromContext arguments may not have a default value: "${parent.coordinate}".`, { nodes: (0, definitions_1.sourceASTs)(application) }));
            }
            if (!context || !selection) {
                errorCollector.push(error_1.ERRORS.NO_CONTEXT_IN_SELECTION.err(`@fromContext argument does not reference a context "${field}".`, { nodes: (0, definitions_1.sourceASTs)(application) }));
            }
            else {
                const locations = contextToTypeMap.get(context);
                if (!locations) {
                    errorCollector.push(error_1.ERRORS.CONTEXT_NOT_SET.err(`Context "${context}" is used at location "${parent.coordinate}" but is never set.`, { nodes: (0, definitions_1.sourceASTs)(application) }));
                }
                else {
                    validateFieldValue({
                        context,
                        selection,
                        fromContextParent: parent,
                        setContextLocations: locations,
                        errorCollector,
                        metadata,
                    });
                }
                const keyDirective = metadata.keyDirective();
                const keyApplications = objectType.appliedDirectivesOf(keyDirective);
                if (!keyApplications.some(app => app.arguments().resolvable || app.arguments().resolvable === undefined)) {
                    errorCollector.push(error_1.ERRORS.CONTEXT_NO_RESOLVABLE_KEY.err(`Object "${objectType.coordinate}" has no resolvable key but has a field with a contextual argument.`, { nodes: (0, definitions_1.sourceASTs)(objectType) }));
                }
            }
        }
        validateNoExternalOnInterfaceFields(metadata, errorCollector);
        validateAllExternalFieldsUsed(metadata, errorCollector);
        validateKeyOnInterfacesAreAlsoOnAllImplementations(metadata, errorCollector);
        validateInterfaceObjectsAreOnEntities(metadata, errorCollector);
        const tagDirective = metadata.tagDirective();
        if (tagDirective) {
            const error = tagSpec.checkCompatibleDirective(tagDirective);
            if (error) {
                errorCollector.push(error);
            }
        }
        for (const objectType of schema.objectTypes()) {
            validateShareableNotRepeatedOnSameDeclaration(objectType, metadata, errorCollector);
            for (const field of objectType.fields()) {
                validateShareableNotRepeatedOnSameDeclaration(field, metadata, errorCollector);
            }
        }
        for (const shareableApplication of metadata.shareableDirective().applications()) {
            const element = shareableApplication.parent;
            if (element instanceof definitions_1.FieldDefinition && !(0, definitions_1.isObjectType)(element.parent)) {
                errorCollector.push(error_1.ERRORS.INVALID_SHAREABLE_USAGE.err(`Invalid use of @shareable on field "${element.coordinate}": only object type fields can be marked with @shareable`, { nodes: (0, definitions_1.sourceASTs)(shareableApplication, element.parent) }));
            }
        }
        const costFeature = (_d = schema.coreFeatures) === null || _d === void 0 ? void 0 : _d.getByIdentity(costSpec_1.costIdentity);
        const costSpec = costFeature && costSpec_1.COST_VERSIONS.find(costFeature.url.version);
        const costDirective = costSpec === null || costSpec === void 0 ? void 0 : costSpec.costDirective(schema);
        const listSizeDirective = costSpec === null || costSpec === void 0 ? void 0 : costSpec.listSizeDirective(schema);
        for (const application of (_e = costDirective === null || costDirective === void 0 ? void 0 : costDirective.applications()) !== null && _e !== void 0 ? _e : []) {
            validateCostNotAppliedToInterface(application, errorCollector);
        }
        for (const application of (_f = listSizeDirective === null || listSizeDirective === void 0 ? void 0 : listSizeDirective.applications()) !== null && _f !== void 0 ? _f : []) {
            const parent = application.parent;
            (0, utils_1.assert)(parent instanceof definitions_1.FieldDefinition, "@listSize can only be applied to FIELD_DEFINITION");
            validateListSizeAppliedToList(application, parent, errorCollector);
            validateAssumedSizeNotNegative(application, parent, errorCollector);
            validateSlicingArgumentsAreValidIntegers(application, parent, errorCollector);
            validateSizedFieldsAreValidLists(application, parent, errorCollector);
        }
        return errorCollector;
    }
    validationRules() {
        return FEDERATION_VALIDATION_RULES;
    }
    onUnknownDirectiveValidationError(schema, unknownDirectiveName, error) {
        const metadata = federationMetadata(schema);
        (0, utils_1.assert)(metadata, `This method should only have been called on a subgraph schema`);
        if (ALL_DEFAULT_FEDERATION_DIRECTIVE_NAMES.includes(unknownDirectiveName)) {
            if (metadata.isFed2Schema()) {
                const federationFeature = metadata.federationFeature();
                (0, utils_1.assert)(federationFeature, 'Fed2 subgraph _must_ link to the federation feature');
                const directiveNameInSchema = federationFeature.directiveNameInSchema(unknownDirectiveName);
                if (directiveNameInSchema.startsWith(federationFeature.nameInSchema + '__')) {
                    return (0, error_1.withModifiedErrorMessage)(error, `${error.message} If you meant the "@${unknownDirectiveName}" federation directive, you should use fully-qualified name "@${directiveNameInSchema}" or add "@${unknownDirectiveName}" to the \`import\` argument of the @link to the federation specification.`);
                }
                else {
                    return (0, error_1.withModifiedErrorMessage)(error, `${error.message} If you meant the "@${unknownDirectiveName}" federation directive, you should use "@${directiveNameInSchema}" as it is imported under that name in the @link to the federation specification of this schema.`);
                }
            }
            else {
                return (0, error_1.withModifiedErrorMessage)(error, `${error.message} If you meant the "@${unknownDirectiveName}" federation 2 directive, note that this schema is a federation 1 schema. To be a federation 2 schema, it needs to @link to the federation specifcation v2.`);
            }
        }
        else if (!metadata.isFed2Schema()) {
            const suggestions = (0, suggestions_1.suggestionList)(unknownDirectiveName, ALL_DEFAULT_FEDERATION_DIRECTIVE_NAMES);
            if (suggestions.length > 0) {
                return (0, error_1.withModifiedErrorMessage)(error, `${error.message}${(0, suggestions_1.didYouMean)(suggestions.map((s) => '@' + s))} If so, note that ${suggestions.length === 1 ? 'it is a federation 2 directive' : 'they are federation 2 directives'} but this schema is a federation 1 one. To be a federation 2 schema, it needs to @link to the federation specifcation v2.`);
            }
        }
        return error;
    }
    applyDirectivesAfterParsing() {
        return true;
    }
}
exports.FederationBlueprint = FederationBlueprint;
function findUnusedNamedForLinkDirective(schema) {
    if (!schema.directive(linkSpec.url.name)) {
        return undefined;
    }
    const baseName = linkSpec.url.name;
    const n = 1;
    for (;;) {
        const candidate = baseName + n;
        if (!schema.directive(candidate)) {
            return candidate;
        }
    }
}
function setSchemaAsFed2Subgraph(schema, useLatest = false) {
    let core = schema.coreFeatures;
    let spec;
    if (core) {
        spec = core.coreDefinition;
        (0, utils_1.assert)(spec.url.version.satisfies(linkSpec.version), `Fed2 schema must use @link with version >= 1.0, but schema uses ${spec.url}`);
    }
    else {
        const alias = findUnusedNamedForLinkDirective(schema);
        const errors = linkSpec.addToSchema(schema, alias);
        if (errors.length > 0) {
            throw (0, definitions_1.ErrGraphQLValidationFailed)(errors);
        }
        spec = linkSpec;
        core = schema.coreFeatures;
        (0, utils_1.assert)(core, 'Schema should now be a core schema');
    }
    const fedSpec = useLatest ? latestFederationSpec : autoExpandedFederationSpec;
    (0, utils_1.assert)(!core.getByIdentity(fedSpec.identity), 'Schema already set as a federation subgraph');
    schema.schemaDefinition.applyDirective(core.coreItself.nameInSchema, {
        url: fedSpec.url.toString(),
        import: autoExpandedFederationSpec.directiveSpecs().map((spec) => `@${spec.name}`),
    });
    const errors = completeSubgraphSchema(schema);
    if (errors.length > 0) {
        throw (0, definitions_1.ErrGraphQLValidationFailed)(errors);
    }
}
exports.setSchemaAsFed2Subgraph = setSchemaAsFed2Subgraph;
exports.FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.10", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresScopes", "@policy", "@context", "@fromContext", "@cost", "@listSize"])';
exports.FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.10", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';
exports.FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS_UPGRADED = '@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';
function asFed2SubgraphDocument(document, options) {
    var _a, _b;
    const importedDirectives = (options === null || options === void 0 ? void 0 : options.includeAllImports) ? latestFederationSpec.directiveSpecs() : autoExpandedFederationSpec.directiveSpecs();
    const directiveToAdd = ({
        kind: graphql_1.Kind.DIRECTIVE,
        name: { kind: graphql_1.Kind.NAME, value: coreSpec_1.linkDirectiveDefaultName },
        arguments: [
            {
                kind: graphql_1.Kind.ARGUMENT,
                name: { kind: graphql_1.Kind.NAME, value: 'url' },
                value: { kind: graphql_1.Kind.STRING, value: latestFederationSpec.url.toString() }
            },
            {
                kind: graphql_1.Kind.ARGUMENT,
                name: { kind: graphql_1.Kind.NAME, value: 'import' },
                value: { kind: graphql_1.Kind.LIST, values: importedDirectives.map((spec) => ({ kind: graphql_1.Kind.STRING, value: `@${spec.name}` })) }
            }
        ]
    });
    if ((_a = options === null || options === void 0 ? void 0 : options.addAsSchemaExtension) !== null && _a !== void 0 ? _a : true) {
        return {
            kind: graphql_1.Kind.DOCUMENT,
            loc: document.loc,
            definitions: document.definitions.concat({
                kind: graphql_1.Kind.SCHEMA_EXTENSION,
                directives: [directiveToAdd]
            }),
        };
    }
    const existingSchemaDefinition = document.definitions.find((d) => d.kind == graphql_1.Kind.SCHEMA_DEFINITION);
    if (existingSchemaDefinition) {
        return {
            kind: graphql_1.Kind.DOCUMENT,
            loc: document.loc,
            definitions: document.definitions.filter((d) => d !== existingSchemaDefinition).concat([{
                    ...existingSchemaDefinition,
                    directives: [directiveToAdd].concat((_b = existingSchemaDefinition.directives) !== null && _b !== void 0 ? _b : []),
                }]),
        };
    }
    else {
        const hasMutation = document.definitions.some((d) => d.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION && d.name.value === 'Mutation');
        const makeOpType = (opType, name) => ({
            kind: graphql_1.Kind.OPERATION_TYPE_DEFINITION,
            operation: opType,
            type: {
                kind: graphql_1.Kind.NAMED_TYPE,
                name: {
                    kind: graphql_1.Kind.NAME,
                    value: name,
                }
            },
        });
        return {
            kind: graphql_1.Kind.DOCUMENT,
            loc: document.loc,
            definitions: document.definitions.concat({
                kind: graphql_1.Kind.SCHEMA_DEFINITION,
                directives: [directiveToAdd],
                operationTypes: [makeOpType(graphql_1.OperationTypeNode.QUERY, 'Query')].concat(hasMutation ? makeOpType(graphql_1.OperationTypeNode.MUTATION, 'Mutation') : []),
            }),
        };
    }
}
exports.asFed2SubgraphDocument = asFed2SubgraphDocument;
function printSubgraphNames(names) {
    return (0, utils_1.printHumanReadableList)(names.map(n => `"${n}"`), {
        prefix: 'subgraph',
        prefixPlural: 'subgraphs',
    });
}
exports.printSubgraphNames = printSubgraphNames;
function federationMetadata(schema) {
    return schema['_federationMetadata'];
}
exports.federationMetadata = federationMetadata;
function isFederationSubgraphSchema(schema) {
    return !!federationMetadata(schema);
}
exports.isFederationSubgraphSchema = isFederationSubgraphSchema;
function isFederationField(field) {
    var _a;
    if (field.parent === ((_a = field.schema().schemaDefinition.root("query")) === null || _a === void 0 ? void 0 : _a.type)) {
        return exports.FEDERATION_OPERATION_FIELDS.includes(field.name);
    }
    return false;
}
exports.isFederationField = isFederationField;
function isEntityType(type) {
    if (!(0, definitions_1.isObjectType)(type) && !(0, definitions_1.isInterfaceType)(type)) {
        return false;
    }
    const metadata = federationMetadata(type.schema());
    return !!metadata && type.hasAppliedDirective(metadata.keyDirective());
}
exports.isEntityType = isEntityType;
function isInterfaceObjectType(type) {
    if (!(0, definitions_1.isObjectType)(type)) {
        return false;
    }
    const metadata = federationMetadata(type.schema());
    return !!metadata && metadata.isInterfaceObjectType(type);
}
exports.isInterfaceObjectType = isInterfaceObjectType;
function buildSubgraph(name, url, source, withRootTypeRenaming = true) {
    const buildOptions = {
        blueprint: new FederationBlueprint(withRootTypeRenaming),
        validate: false,
    };
    let subgraph;
    try {
        const schema = typeof source === 'string'
            ? (0, buildSchema_1.buildSchema)(new graphql_1.Source(source, name), buildOptions)
            : (0, buildSchema_1.buildSchemaFromAST)(source, buildOptions);
        subgraph = new Subgraph(name, url, schema);
    }
    catch (e) {
        if (e instanceof graphql_1.GraphQLError && name !== exports.FEDERATION_UNNAMED_SUBGRAPH_NAME) {
            throw addSubgraphToError(e, name, error_1.ERRORS.INVALID_GRAPHQL);
        }
        else {
            throw e;
        }
    }
    return subgraph.validate();
}
exports.buildSubgraph = buildSubgraph;
function newEmptyFederation2Schema(config) {
    const schema = new definitions_1.Schema(new FederationBlueprint(true), config);
    setSchemaAsFed2Subgraph(schema, true);
    return schema;
}
exports.newEmptyFederation2Schema = newEmptyFederation2Schema;
function completeSubgraphSchema(schema) {
    const coreFeatures = schema.coreFeatures;
    if (coreFeatures) {
        const fedFeature = coreFeatures.getByIdentity(federationSpec_1.federationIdentity);
        if (fedFeature) {
            return completeFed2SubgraphSchema(schema);
        }
        else {
            return completeFed1SubgraphSchema(schema);
        }
    }
    else {
        const fedLink = schema.schemaDefinition.appliedDirectivesOf(coreSpec_1.linkDirectiveDefaultName).find(isFedSpecLinkDirective);
        if (fedLink) {
            const errors = linkSpec.addToSchema(schema);
            if (errors.length > 0) {
                return errors;
            }
            return completeFed2SubgraphSchema(schema);
        }
        else {
            return completeFed1SubgraphSchema(schema);
        }
    }
}
function isFedSpecLinkDirective(directive) {
    const args = directive.arguments();
    return directive.name === coreSpec_1.linkDirectiveDefaultName && args['url'] && args['url'].startsWith(federationSpec_1.federationIdentity);
}
function completeFed1SubgraphSchema(schema) {
    var _a, _b;
    for (const name of [federationSpec_1.FederationDirectiveName.KEY, federationSpec_1.FederationDirectiveName.PROVIDES, federationSpec_1.FederationDirectiveName.REQUIRES]) {
        const directive = schema.directive(name);
        if (!directive) {
            continue;
        }
        (0, utils_1.assert)(directive.applications().size === 0, `${directive} shouldn't have had validation at that places`);
        const fieldType = (_b = (_a = directive.argument('fields')) === null || _a === void 0 ? void 0 : _a.type) === null || _b === void 0 ? void 0 : _b.toString();
        const fieldTypeIsWrongInKnownWays = !!fieldType
            && directive.arguments().length === 1
            && (fieldType === 'String' || fieldType === '_FieldSet' || fieldType === 'FieldSet');
        if (directive.arguments().length === 0 || fieldTypeIsWrongInKnownWays) {
            directive.remove();
        }
    }
    const errors = federationSpec_1.FEDERATION1_TYPES.map((spec) => spec.checkOrAdd(schema, FAKE_FED1_CORE_FEATURE_TO_RENAME_TYPES))
        .concat(federationSpec_1.FEDERATION1_DIRECTIVES.map((spec) => spec.checkOrAdd(schema)))
        .flat();
    return errors.length === 0 ? expandKnownFeatures(schema) : errors;
}
function completeFed2SubgraphSchema(schema) {
    const coreFeatures = schema.coreFeatures;
    (0, utils_1.assert)(coreFeatures, 'This method should not have been called on a non-core schema');
    const fedFeature = coreFeatures.getByIdentity(federationSpec_1.federationIdentity);
    (0, utils_1.assert)(fedFeature, 'This method should not have been called on a schema with no @link for federation');
    const spec = federationSpec_1.FEDERATION_VERSIONS.find(fedFeature.url.version);
    if (!spec) {
        return [error_1.ERRORS.UNKNOWN_FEDERATION_LINK_VERSION.err(`Invalid version ${fedFeature.url.version} for the federation feature in @link directive on schema`, { nodes: fedFeature.directive.sourceAST })];
    }
    const errors = spec.addElementsToSchema(schema);
    return errors.length === 0 ? expandKnownFeatures(schema) : errors;
}
function expandKnownFeatures(schema) {
    const coreFeatures = schema.coreFeatures;
    if (!coreFeatures) {
        return [];
    }
    let errors = [];
    for (const feature of coreFeatures.allFeatures()) {
        if (feature === coreFeatures.coreItself || feature.url.identity === federationSpec_1.federationIdentity || feature.url.identity === joinSpec_1.joinIdentity) {
            continue;
        }
        const spec = (0, knownCoreFeatures_1.coreFeatureDefinitionIfKnown)(feature.url);
        if (!spec) {
            continue;
        }
        errors = errors.concat(spec.addElementsToSchema(schema));
    }
    return errors;
}
function parseFieldSetArgument({ parentType, directive, fieldAccessor, validate, decorateValidationErrors = true, normalize = false, }) {
    try {
        const selectionSet = (0, operations_1.parseSelectionSet)({
            parentType,
            source: validateFieldSetValue(directive),
            fieldAccessor,
            validate,
        });
        if (validate !== null && validate !== void 0 ? validate : true) {
            selectionSet.forEachElement((elt) => {
                if (elt.kind === 'Field' && elt.alias) {
                    throw new graphql_1.GraphQLError(`Cannot use alias "${elt.alias}" in "${elt}": aliases are not currently supported in @${directive.name}`);
                }
            });
        }
        return normalize
            ? selectionSet.normalize({ parentType, recursive: true })
            : selectionSet;
    }
    catch (e) {
        if (!(e instanceof graphql_1.GraphQLError) || !decorateValidationErrors) {
            throw e;
        }
        throw handleFieldSetValidationError(directive, e, (msg) => {
            if (msg.startsWith('Cannot query field')) {
                if (msg.endsWith('.')) {
                    msg = msg.slice(0, msg.length - 1);
                }
                if (directive.name === federationSpec_1.FederationDirectiveName.KEY) {
                    msg = msg + ' (the field should either be added to this subgraph or, if it should not be resolved by this subgraph, you need to add it to this subgraph with @external).';
                }
                else {
                    msg = msg + ' (if the field is defined in another subgraph, you need to add it to this subgraph with @external).';
                }
            }
            return msg;
        });
    }
}
exports.parseFieldSetArgument = parseFieldSetArgument;
function collectTargetFields({ parentType, directive, includeInterfaceFieldsImplementations, validate = true, }) {
    const fields = [];
    try {
        parseFieldSetArgument({
            parentType,
            directive,
            fieldAccessor: (t, f) => {
                const field = t.field(f);
                if (field) {
                    fields.push(field);
                    if (includeInterfaceFieldsImplementations && (0, definitions_1.isInterfaceType)(t)) {
                        for (const implType of t.possibleRuntimeTypes()) {
                            const implField = implType.field(f);
                            if (implField) {
                                fields.push(implField);
                            }
                        }
                    }
                }
                return field;
            },
            validate,
        });
    }
    catch (e) {
        const isGraphQLError = (0, error_1.errorCauses)(e) !== undefined;
        if (!isGraphQLError || validate) {
            throw e;
        }
    }
    return fields;
}
exports.collectTargetFields = collectTargetFields;
function validateFieldSetValue(directive) {
    var _a;
    const fields = directive.arguments().fields;
    const nodes = directive.sourceAST;
    if (typeof fields !== 'string') {
        throw error_1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS_TYPE.get(directive.name).err(`Invalid value for argument "${directive.definition.argument('fields').name}": must be a string.`, { nodes });
    }
    if (nodes && nodes.kind === 'Directive') {
        for (const argNode of (_a = nodes.arguments) !== null && _a !== void 0 ? _a : []) {
            if (argNode.name.value === 'fields') {
                if (argNode.value.kind !== 'StringValue') {
                    throw error_1.ERROR_CATEGORIES.DIRECTIVE_INVALID_FIELDS_TYPE.get(directive.name).err(`Invalid value for argument "${directive.definition.argument('fields').name}": must be a string.`, { nodes });
                }
                break;
            }
        }
    }
    return fields;
}
function subgraphsFromServiceList(serviceList) {
    var _a;
    let errors = [];
    const subgraphs = new Subgraphs();
    for (const service of serviceList) {
        try {
            subgraphs.add(buildSubgraph(service.name, (_a = service.url) !== null && _a !== void 0 ? _a : '', service.typeDefs));
        }
        catch (e) {
            const causes = (0, error_1.errorCauses)(e);
            if (causes) {
                errors = errors.concat(causes);
            }
            else {
                throw e;
            }
        }
    }
    return errors.length === 0 ? subgraphs : errors;
}
exports.subgraphsFromServiceList = subgraphsFromServiceList;
class Subgraphs {
    constructor() {
        this.subgraphs = new utils_1.OrderedMap();
    }
    add(subgraph) {
        if (this.subgraphs.has(subgraph.name)) {
            throw new Error(`A subgraph named ${subgraph.name} already exists` + (subgraph.url ? ` (with url '${subgraph.url}')` : ''));
        }
        this.subgraphs.add(subgraph.name, subgraph);
        return subgraph;
    }
    get(name) {
        return this.subgraphs.get(name);
    }
    size() {
        return this.subgraphs.size;
    }
    names() {
        return this.subgraphs.keys();
    }
    values() {
        return this.subgraphs.values();
    }
    *[Symbol.iterator]() {
        for (const subgraph of this.subgraphs) {
            yield subgraph;
        }
    }
    validate() {
        let errors = [];
        for (const subgraph of this.values()) {
            try {
                subgraph.validate();
            }
            catch (e) {
                const causes = (0, error_1.errorCauses)(e);
                if (!causes) {
                    throw e;
                }
                errors = errors.concat(causes);
            }
        }
        return errors.length === 0 ? undefined : errors;
    }
    toString() {
        return '[' + this.subgraphs.keys().join(', ') + ']';
    }
}
exports.Subgraphs = Subgraphs;
exports.anyTypeSpec = (0, directiveAndTypeSpecification_1.createScalarTypeSpecification)({ name: '_Any' });
exports.serviceTypeSpec = (0, directiveAndTypeSpecification_1.createObjectTypeSpecification)({
    name: '_Service',
    fieldsFct: (schema) => [{ name: 'sdl', type: schema.stringType() }],
});
exports.entityTypeSpec = (0, directiveAndTypeSpecification_1.createUnionTypeSpecification)({
    name: '_Entity',
    membersFct: (schema) => {
        return schema.objectTypes().filter(isEntityType).map((t) => t.name);
    },
});
exports.FEDERATION_OPERATION_TYPES = [exports.anyTypeSpec, exports.serviceTypeSpec, exports.entityTypeSpec];
exports.serviceFieldName = '_service';
exports.entitiesFieldName = '_entities';
exports.FEDERATION_OPERATION_FIELDS = [exports.serviceFieldName, exports.entitiesFieldName];
class Subgraph {
    constructor(name, url, schema) {
        this.name = name;
        this.url = url;
        this.schema = schema;
        if (name === exports.FEDERATION_RESERVED_SUBGRAPH_NAME) {
            throw error_1.ERRORS.INVALID_SUBGRAPH_NAME.err(`Invalid name ${exports.FEDERATION_RESERVED_SUBGRAPH_NAME} for a subgraph: this name is reserved`);
        }
    }
    metadata() {
        const metadata = federationMetadata(this.schema);
        (0, utils_1.assert)(metadata, 'The subgraph schema should have built with the federation built-ins.');
        return metadata;
    }
    isFed2Subgraph() {
        return this.metadata().isFed2Schema();
    }
    addFederationOperations() {
        const metadata = this.metadata();
        for (const type of exports.FEDERATION_OPERATION_TYPES) {
            type.checkOrAdd(this.schema);
        }
        const queryRoot = this.schema.schemaDefinition.root("query");
        const queryType = queryRoot ? queryRoot.type : this.schema.addType(new definitions_1.ObjectType("Query"));
        const entityField = queryType.field(exports.entitiesFieldName);
        const entityType = metadata.entityType();
        if (entityType) {
            const entityFieldType = new definitions_1.NonNullType(new definitions_1.ListType(entityType));
            if (!entityField) {
                queryType.addField(exports.entitiesFieldName, entityFieldType)
                    .addArgument('representations', new definitions_1.NonNullType(new definitions_1.ListType(new definitions_1.NonNullType(metadata.anyType()))));
            }
            else if (!entityField.type) {
                entityField.type = entityType;
            }
        }
        else if (entityField) {
            entityField.remove();
        }
        if (!queryType.field(exports.serviceFieldName)) {
            queryType.addField(exports.serviceFieldName, new definitions_1.NonNullType(metadata.serviceType()));
        }
    }
    assumeValid() {
        this.addFederationOperations();
        this.schema.assumeValid();
        return this;
    }
    validate() {
        try {
            this.addFederationOperations();
            this.schema.validate();
            return this;
        }
        catch (e) {
            if (e instanceof graphql_1.GraphQLError) {
                throw addSubgraphToError(e, this.name, error_1.ERRORS.INVALID_GRAPHQL);
            }
            else {
                throw e;
            }
        }
    }
    isPrintedDirective(d) {
        var _a;
        if (this.metadata().allFederationDirectives().includes(d)) {
            return false;
        }
        const core = this.schema.coreFeatures;
        return !core || ((_a = core.sourceFeature(d)) === null || _a === void 0 ? void 0 : _a.feature.url.identity) !== coreSpec_1.linkIdentity;
    }
    isPrintedType(t) {
        var _a;
        if (this.metadata().allFederationTypes().includes(t)) {
            return false;
        }
        if ((0, definitions_1.isObjectType)(t) && t.isQueryRootType() && t.fields().filter((f) => !isFederationField(f)).length === 0) {
            return false;
        }
        const core = this.schema.coreFeatures;
        return !core || ((_a = core.sourceFeature(t)) === null || _a === void 0 ? void 0 : _a.feature.url.identity) !== coreSpec_1.linkIdentity;
    }
    isPrintedDirectiveApplication(d) {
        if (!this.schema.coreFeatures || d.name !== linkSpec.url.name) {
            return true;
        }
        const args = d.arguments();
        let urlArg = undefined;
        if ('url' in args) {
            try {
                urlArg = coreSpec_1.FeatureUrl.parse(args['url']);
            }
            catch (e) {
            }
        }
        const isDefaultLinkToLink = (urlArg === null || urlArg === void 0 ? void 0 : urlArg.identity) === coreSpec_1.linkIdentity && Object.keys(args).length === 1;
        return !isDefaultLinkToLink;
    }
    toString(basePrintOptions = print_1.defaultPrintOptions) {
        return (0, print_1.printSchema)(this.schema, {
            ...basePrintOptions,
            directiveDefinitionFilter: (d) => this.isPrintedDirective(d),
            typeFilter: (t) => this.isPrintedType(t),
            fieldFilter: (f) => !isFederationField(f),
            directiveApplicationFilter: (d) => this.isPrintedDirectiveApplication(d),
        });
    }
}
exports.Subgraph = Subgraph;
function addSubgraphToASTNode(node, subgraph) {
    if ('subgraph' in node) {
        return node;
    }
    return {
        ...node,
        subgraph
    };
}
exports.addSubgraphToASTNode = addSubgraphToASTNode;
function addSubgraphToError(e, subgraphName, errorCode) {
    const updatedCauses = (0, error_1.errorCauses)(e).map(cause => {
        var _a;
        const message = `[${subgraphName}] ${cause.message}`;
        const nodes = cause.nodes
            ? cause.nodes.map(node => addSubgraphToASTNode(node, subgraphName))
            : undefined;
        const code = (_a = (0, error_1.errorCodeDef)(cause)) !== null && _a !== void 0 ? _a : errorCode;
        const options = {
            ...(0, error_1.extractGraphQLErrorOptions)(cause),
            nodes,
            originalError: cause,
        };
        return code
            ? code.err(message, options)
            : new graphql_1.GraphQLError(message, options);
    });
    return updatedCauses.length === 1 ? updatedCauses[0] : (0, definitions_1.ErrGraphQLValidationFailed)(updatedCauses);
}
exports.addSubgraphToError = addSubgraphToError;
class ExternalTester {
    constructor(schema, isFed2Schema) {
        this.schema = schema;
        this.isFed2Schema = isFed2Schema;
        this.fakeExternalFields = new Set();
        this.providedFields = new Set();
        this.externalFieldsOnType = new Set();
        this.externalDirective = this.metadata().externalDirective();
        this.collectFakeExternals();
        this.collectProvidedFields();
        this.collectExternalsOnType();
    }
    metadata() {
        const metadata = federationMetadata(this.schema);
        (0, utils_1.assert)(metadata, 'Schema should be a subgraphs schema');
        return metadata;
    }
    collectFakeExternals() {
        const metadata = this.metadata();
        const extendsDirective = metadata.extendsDirective();
        for (const key of metadata.keyDirective().applications()) {
            const parentType = key.parent;
            if (!(key.ofExtension() || parentType.hasAppliedDirective(extendsDirective))) {
                continue;
            }
            collectTargetFields({
                parentType,
                directive: key,
                includeInterfaceFieldsImplementations: false,
                validate: false,
            }).filter((field) => field.hasAppliedDirective(this.externalDirective))
                .forEach((field) => this.fakeExternalFields.add(field.coordinate));
        }
    }
    collectProvidedFields() {
        for (const provides of this.metadata().providesDirective().applications()) {
            const parent = provides.parent;
            const parentType = (0, definitions_1.baseType)(parent.type);
            if ((0, definitions_1.isCompositeType)(parentType)) {
                collectTargetFields({
                    parentType,
                    directive: provides,
                    includeInterfaceFieldsImplementations: true,
                    validate: false,
                }).forEach((f) => this.providedFields.add(f.coordinate));
            }
        }
    }
    collectExternalsOnType() {
        if (!this.isFed2Schema) {
            return;
        }
        for (const type of this.schema.objectTypes()) {
            if (type.hasAppliedDirective(this.externalDirective)) {
                for (const field of type.fields()) {
                    this.externalFieldsOnType.add(field.coordinate);
                }
            }
        }
    }
    isExternal(field) {
        return (field.hasAppliedDirective(this.externalDirective) || this.externalFieldsOnType.has(field.coordinate)) && !this.isFakeExternal(field);
    }
    isFakeExternal(field) {
        return this.fakeExternalFields.has(field.coordinate);
    }
    selectsAnyExternalField(selectionSet) {
        for (const selection of selectionSet.selections()) {
            if (selection.kind === 'FieldSelection' && this.isExternal(selection.element.definition)) {
                return true;
            }
            if (selection.selectionSet) {
                if (this.selectsAnyExternalField(selection.selectionSet)) {
                    return true;
                }
            }
        }
        return false;
    }
    isPartiallyExternal(field) {
        return this.isExternal(field) && this.providedFields.has(field.coordinate);
    }
    isFullyExternal(field) {
        return this.isExternal(field) && !this.providedFields.has(field.coordinate);
    }
}
function removeInactiveProvidesAndRequires(schema, onModified = () => { }) {
    const metadata = federationMetadata(schema);
    if (!metadata) {
        return;
    }
    const providesDirective = metadata.providesDirective();
    const requiresDirective = metadata.requiresDirective();
    for (const type of schema.types()) {
        if (!(0, definitions_1.isObjectType)(type) && !(0, definitions_1.isInterfaceType)(type)) {
            continue;
        }
        for (const field of type.fields()) {
            const fieldBaseType = (0, definitions_1.baseType)(field.type);
            removeInactiveApplications(providesDirective, field, fieldBaseType, onModified);
            removeInactiveApplications(requiresDirective, field, type, onModified);
        }
    }
}
exports.removeInactiveProvidesAndRequires = removeInactiveProvidesAndRequires;
function removeInactiveApplications(directiveDefinition, field, parentType, onModified) {
    for (const application of field.appliedDirectivesOf(directiveDefinition)) {
        let selection;
        try {
            selection = parseFieldSetArgument({ parentType, directive: application });
        }
        catch (e) {
            continue;
        }
        if (selectsNonExternalLeafField(selection)) {
            application.remove();
            const updated = withoutNonExternalLeafFields(selection);
            if (!updated.isEmpty()) {
                const updatedDirective = field.applyDirective(directiveDefinition, { fields: updated.toString(true, false) });
                onModified(field, application, updatedDirective);
            }
            else {
                onModified(field, application);
            }
        }
    }
}
function isExternalOrHasExternalImplementations(field) {
    const metadata = federationMetadata(field.schema());
    if (!metadata) {
        return false;
    }
    if (field.hasAppliedDirective(metadata.externalDirective())) {
        return true;
    }
    const parentType = field.parent;
    if ((0, definitions_1.isInterfaceType)(parentType)) {
        for (const implem of parentType.possibleRuntimeTypes()) {
            const fieldInImplem = implem.field(field.name);
            if (fieldInImplem && fieldInImplem.hasAppliedDirective(metadata.externalDirective())) {
                return true;
            }
        }
    }
    return false;
}
function selectsNonExternalLeafField(selection) {
    return selection.selections().some(s => {
        if (s.kind === 'FieldSelection') {
            if (isExternalOrHasExternalImplementations(s.element.definition)) {
                return false;
            }
            return !s.selectionSet || selectsNonExternalLeafField(s.selectionSet);
        }
        else {
            return selectsNonExternalLeafField(s.selectionSet);
        }
    });
}
function withoutNonExternalLeafFields(selectionSet) {
    return selectionSet.lazyMap((selection) => {
        if (selection.kind === 'FieldSelection') {
            if (isExternalOrHasExternalImplementations(selection.element.definition)) {
                return selection;
            }
        }
        if (selection.selectionSet) {
            const updated = withoutNonExternalLeafFields(selection.selectionSet);
            if (!updated.isEmpty()) {
                return selection.withUpdatedSelectionSet(updated);
            }
        }
        return undefined;
    });
}
//# sourceMappingURL=federation.js.map