@apollo/federation-internals
Version:
Apollo Federation internal utilities
276 lines • 15.7 kB
JavaScript
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
;