UNPKG

@apollo/federation-internals

Version:
276 lines 15.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createEnumTypeSpecification = exports.createUnionTypeSpecification = exports.createObjectTypeSpecification = exports.createScalarTypeSpecification = exports.createDirectiveSpecification = void 0; const graphql_1 = require("graphql"); const definitions_1 = require("./definitions"); const error_1 = require("./error"); const values_1 = require("./values"); const types_1 = require("./types"); const utils_1 = require("./utils"); function createDirectiveSpecification({ name, locations, repeatable = false, args = [], composes = false, supergraphSpecification = undefined, staticArgumentTransform = undefined, }) { let composition = undefined; if (composes) { (0, utils_1.assert)(supergraphSpecification, `Should provide a @link specification to use in supergraph for directive @${name} if it composes`); const argStrategies = new Map(args.filter((arg) => arg.compositionStrategy).map((arg) => [arg.name, arg.compositionStrategy])); let argumentsMerger = undefined; if (argStrategies.size > 0) { (0, utils_1.assert)(!repeatable, () => `Invalid directive specification for @${name}: @${name} is repeatable and should not define composition strategy for its arguments`); (0, utils_1.assert)(argStrategies.size === args.length, () => `Invalid directive specification for @${name}: not all arguments define a composition strategy`); argumentsMerger = (schema, feature) => { for (const { name: argName, type } of args) { const strategy = argStrategies.get(argName); (0, utils_1.assert)(strategy, () => `Should have a strategy for ${argName}`); const argType = type(schema, feature); (0, utils_1.assert)(!Array.isArray(argType), () => `Should have gotten error getting type for @${name}(${argName}:), but got ${argType}`); const { valid, supportedMsg } = strategy.isTypeSupported(schema, argType); if (!valid) { return new graphql_1.GraphQLError(`Invalid composition strategy ${strategy.name} for argument @${name}(${argName}:) of type ${argType}; ` + `${strategy.name} only supports ${supportedMsg}`); } } return { merge: (argName, values) => { const strategy = argStrategies.get(argName); (0, utils_1.assert)(strategy, () => `Should have a strategy for ${argName}`); return strategy.mergeValues(values); }, toString: () => { if (argStrategies.size === 0) { return "<none>"; } return '{ ' + [...argStrategies.entries()].map(([arg, strategy]) => `"${arg}": ${strategy.name}`).join(', ') + ' }'; } }; }; } composition = { supergraphSpecification, argumentsMerger, staticArgumentTransform, }; } return { name, composition, checkOrAdd: (schema, feature, asBuiltIn) => { var _a; const actualName = (_a = feature === null || feature === void 0 ? void 0 : feature.directiveNameInSchema(name)) !== null && _a !== void 0 ? _a : name; const { resolvedArgs, errors } = args.reduce(({ resolvedArgs, errors }, arg) => { const typeOrErrors = arg.type(schema, feature); if (Array.isArray(typeOrErrors)) { errors.push(...typeOrErrors); } else { resolvedArgs.push({ ...arg, type: typeOrErrors }); } return { resolvedArgs, errors }; }, { resolvedArgs: [], errors: [] }); if (errors.length > 0) { return errors; } const existing = schema.directive(actualName); if (existing) { return ensureSameDirectiveStructure({ name: actualName, locations, repeatable, args: resolvedArgs }, existing); } else { const directive = schema.addDirectiveDefinition(new definitions_1.DirectiveDefinition(actualName, asBuiltIn)); directive.repeatable = repeatable; directive.addLocations(...locations); for (const { name, type, defaultValue } of resolvedArgs) { directive.addArgument(name, type, defaultValue); } return []; } }, }; } exports.createDirectiveSpecification = createDirectiveSpecification; function createScalarTypeSpecification({ name }) { return { name, checkOrAdd: (schema, feature, asBuiltIn) => { var _a; const actualName = (_a = feature === null || feature === void 0 ? void 0 : feature.typeNameInSchema(name)) !== null && _a !== void 0 ? _a : name; const existing = schema.type(actualName); if (existing) { return ensureSameTypeKind('ScalarType', existing); } else { schema.addType(new definitions_1.ScalarType(actualName, asBuiltIn)); return []; } }, }; } exports.createScalarTypeSpecification = createScalarTypeSpecification; function createObjectTypeSpecification({ name, fieldsFct, }) { return { name, checkOrAdd: (schema, feature, asBuiltIn) => { var _a; const actualName = (_a = feature === null || feature === void 0 ? void 0 : feature.typeNameInSchema(name)) !== null && _a !== void 0 ? _a : name; const expectedFields = fieldsFct(schema); const existing = schema.type(actualName); if (existing) { let errors = ensureSameTypeKind('ObjectType', existing); if (errors.length > 0) { return errors; } (0, utils_1.assert)((0, definitions_1.isObjectType)(existing), 'Should be an object type'); for (const { name, type, args } of expectedFields) { const existingField = existing.field(name); if (!existingField) { errors = errors.concat(error_1.ERRORS.TYPE_DEFINITION_INVALID.err(`Invalid definition of type ${name}: missing field ${name}`, { nodes: existing.sourceAST })); continue; } let existingType = existingField.type; if (!(0, definitions_1.isNonNullType)(type) && (0, definitions_1.isNonNullType)(existingType)) { existingType = existingType.ofType; } if (!(0, types_1.sameType)(type, existingType)) { errors = errors.concat(error_1.ERRORS.TYPE_DEFINITION_INVALID.err(`Invalid definition for field ${name} of type ${name}: should have type ${type} but found type ${existingField.type}`, { nodes: existingField.sourceAST })); } errors = errors.concat(ensureSameArguments({ name, args }, existingField, `field "${existingField.coordinate}"`)); } return errors; } else { const createdType = schema.addType(new definitions_1.ObjectType(actualName, asBuiltIn)); for (const { name, type, args } of expectedFields) { const field = createdType.addField(name, type); for (const { name: argName, type: argType, defaultValue } of args !== null && args !== void 0 ? args : []) { field.addArgument(argName, argType, defaultValue); } } return []; } }, }; } exports.createObjectTypeSpecification = createObjectTypeSpecification; function createUnionTypeSpecification({ name, membersFct, }) { return { name, checkOrAdd: (schema, feature, asBuiltIn) => { var _a; const actualName = (_a = feature === null || feature === void 0 ? void 0 : feature.typeNameInSchema(name)) !== null && _a !== void 0 ? _a : name; const existing = schema.type(actualName); const expectedMembers = membersFct(schema).sort((n1, n2) => n1.localeCompare(n2)); if (expectedMembers.length === 0) { if (existing) { return [error_1.ERRORS.TYPE_DEFINITION_INVALID.err(`Invalid definition of type ${name}: expected the union type to not exist/have no members but it is defined.`, { nodes: existing.sourceAST })]; } return []; } if (existing) { let errors = ensureSameTypeKind('UnionType', existing); if (errors.length > 0) { return errors; } (0, utils_1.assert)((0, definitions_1.isUnionType)(existing), 'Should be an union type'); const actualMembers = existing.members().map(m => m.type.name).sort((n1, n2) => n1.localeCompare(n2)); if (!(0, utils_1.arrayEquals)(expectedMembers, actualMembers)) { errors = errors.concat(error_1.ERRORS.TYPE_DEFINITION_INVALID.err(`Invalid definition of type ${name}: expected members [${expectedMembers}] but found [${actualMembers}].`, { nodes: existing.sourceAST })); } return errors; } else { const type = schema.addType(new definitions_1.UnionType(actualName, asBuiltIn)); for (const member of expectedMembers) { type.addType(member); } return []; } }, }; } exports.createUnionTypeSpecification = createUnionTypeSpecification; function createEnumTypeSpecification({ name, values, }) { return { name, checkOrAdd: (schema, feature, asBuiltIn) => { var _a; const actualName = (_a = feature === null || feature === void 0 ? void 0 : feature.typeNameInSchema(name)) !== null && _a !== void 0 ? _a : name; const existing = schema.type(actualName); const expectedValueNames = values.map((v) => v.name).sort((n1, n2) => n1.localeCompare(n2)); if (existing) { let errors = ensureSameTypeKind('EnumType', existing); if (errors.length > 0) { return errors; } (0, utils_1.assert)((0, definitions_1.isEnumType)(existing), 'Should be an enum type'); const actualValueNames = existing.values.map(v => v.name).sort((n1, n2) => n1.localeCompare(n2)); if (!(0, utils_1.arrayEquals)(expectedValueNames, actualValueNames)) { errors = errors.concat(error_1.ERRORS.TYPE_DEFINITION_INVALID.err(`Invalid definition for type "${name}": expected values [${expectedValueNames.join(', ')}] but found [${actualValueNames.join(', ')}].`, { nodes: existing.sourceAST })); } return errors; } else { const type = schema.addType(new definitions_1.EnumType(actualName, asBuiltIn)); for (const { name, description } of values) { type.addValue(name).description = description; } return []; } }, }; } exports.createEnumTypeSpecification = createEnumTypeSpecification; function ensureSameTypeKind(expected, actual) { return expected === actual.kind ? [] : [ error_1.ERRORS.TYPE_DEFINITION_INVALID.err(`Invalid definition for type ${actual.name}: ${actual.name} should be a ${expected} but is defined as a ${actual.kind}`, { nodes: actual.sourceAST }) ]; } function ensureSameDirectiveStructure(expected, actual) { const directiveName = `"@${expected.name}"`; let errors = ensureSameArguments(expected, actual, `directive ${directiveName}`); if (!expected.repeatable && actual.repeatable) { errors = errors.concat(error_1.ERRORS.DIRECTIVE_DEFINITION_INVALID.err(`Invalid definition for directive ${directiveName}: ${directiveName} should${expected.repeatable ? "" : " not"} be repeatable`, { nodes: actual.sourceAST })); } if (!actual.locations.every(loc => expected.locations.includes(loc))) { errors = errors.concat(error_1.ERRORS.DIRECTIVE_DEFINITION_INVALID.err(`Invalid definition for directive ${directiveName}: ${directiveName} should have locations ${expected.locations.join(', ')}, but found (non-subset) ${actual.locations.join(', ')}`, { nodes: actual.sourceAST })); } return errors; } function ensureSameArguments(expected, actual, what, containerSourceAST) { var _a; const expectedArguments = (_a = expected.args) !== null && _a !== void 0 ? _a : []; const errors = []; for (const { name, type, defaultValue } of expectedArguments) { const actualArgument = actual.argument(name); if (!actualArgument) { if ((0, definitions_1.isNonNullType)(type) && defaultValue === undefined) { errors.push(error_1.ERRORS.DIRECTIVE_DEFINITION_INVALID.err(`Invalid definition for ${what}: missing required argument "${name}"`, { nodes: containerSourceAST })); } continue; } let actualType = actualArgument.type; if ((0, definitions_1.isNonNullType)(actualType) && !(0, definitions_1.isNonNullType)(type)) { actualType = actualType.ofType; } if (!(0, types_1.sameType)(type, actualType) && !isValidInputTypeRedefinition(type, actualType)) { errors.push(error_1.ERRORS.DIRECTIVE_DEFINITION_INVALID.err(`Invalid definition for ${what}: argument "${name}" should have type "${type}" but found type "${actualArgument.type}"`, { nodes: actualArgument.sourceAST })); } else if (!(0, definitions_1.isNonNullType)(actualArgument.type) && !(0, values_1.valueEquals)(defaultValue, actualArgument.defaultValue)) { errors.push(error_1.ERRORS.DIRECTIVE_DEFINITION_INVALID.err(`Invalid definition for ${what}: argument "${name}" should have default value ${(0, values_1.valueToString)(defaultValue)} but found default value ${(0, values_1.valueToString)(actualArgument.defaultValue)}`, { nodes: actualArgument.sourceAST })); } } for (const actualArgument of actual.arguments()) { if (!expectedArguments.some((arg) => arg.name === actualArgument.name)) { errors.push(error_1.ERRORS.DIRECTIVE_DEFINITION_INVALID.err(`Invalid definition for ${what}: unknown/unsupported argument "${actualArgument.name}"`, { nodes: actualArgument.sourceAST })); } } return errors; } function isValidInputTypeRedefinition(expectedType, actualType) { if ((0, definitions_1.isListType)(expectedType)) { return (0, definitions_1.isListType)(actualType) && isValidInputTypeRedefinition(expectedType.ofType, actualType.ofType); } if ((0, definitions_1.isNonNullType)(expectedType)) { return (0, definitions_1.isNonNullType)(actualType) && isValidInputTypeRedefinition(expectedType.ofType, actualType.ofType); } return (0, definitions_1.isCustomScalarType)(expectedType) && !(0, definitions_1.isCustomScalarType)(actualType); } //# sourceMappingURL=directiveAndTypeSpecification.js.map