UNPKG

@apollo/federation-internals

Version:
242 lines 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateSchema = void 0; const definitions_1 = require("./definitions"); const graphql_1 = require("graphql"); const values_1 = require("./values"); const introspection_1 = require("./introspection"); const types_1 = require("./types"); const error_1 = require("./error"); function validateSchema(schema) { return new Validator(schema).validate(); } exports.validateSchema = validateSchema; class InputObjectCircularRefsValidator { constructor(onError) { this.onError = onError; this.visitedTypes = new Set(); this.fieldPath = []; this.fieldPathIndexByTypeName = new Map(); } detectCycles(type) { if (this.visitedTypes.has(type.name)) { return; } this.visitedTypes.add(type.name); this.fieldPathIndexByTypeName.set(type.name, this.fieldPath.length); for (const field of type.fields()) { if ((0, definitions_1.isNonNullType)(field.type) && (0, definitions_1.isInputObjectType)(field.type.ofType)) { const fieldType = field.type.ofType; const cycleIndex = this.fieldPathIndexByTypeName.get(fieldType.name); this.fieldPath.push(field); if (cycleIndex === undefined) { this.detectCycles(fieldType); } else { const cyclePath = this.fieldPath.slice(cycleIndex); const pathStr = cyclePath.map((fieldObj) => fieldObj.name).join('.'); this.onError(`Cannot reference Input Object "${fieldType.name}" within itself through a series of non-null fields: "${pathStr}".`, { nodes: (0, definitions_1.sourceASTs)(...cyclePath) }); } this.fieldPath.pop(); } } this.fieldPathIndexByTypeName.delete(type.name); } } class Validator { constructor(schema) { this.schema = schema; this.emptyVariables = new definitions_1.VariableDefinitions(); this.hasMissingTypes = false; this.errors = []; } validate() { for (const type of this.schema.types()) { if (!introspection_1.introspectionTypeNames.includes(type.name)) { this.validateName(type); } switch (type.kind) { case 'ObjectType': case 'InterfaceType': this.validateObjectOrInterfaceType(type); break; case 'InputObjectType': this.validateInputObjectType(type); break; case 'UnionType': this.validateUnionType(type); break; case 'EnumType': this.validateEnumType(type); break; } } for (const directive of this.schema.allDirectives()) { this.validateName(directive); for (const arg of directive.arguments()) { this.validateArg(arg); } for (const application of directive.applications()) { this.validateDirectiveApplication(directive, application); } } if (!this.hasMissingTypes) { const refsValidator = new InputObjectCircularRefsValidator((msg, opts) => this.addError(msg, opts)); for (const type of this.schema.types()) { switch (type.kind) { case 'ObjectType': case 'InterfaceType': this.validateImplementedInterfaces(type); break; case 'InputObjectType': refsValidator.detectCycles(type); break; } } } return this.errors; } addError(message, options) { this.errors.push(error_1.ERRORS.INVALID_GRAPHQL.err(message, options)); } validateHasType(elt) { if (!elt.type) { this.addError(`Element ${elt.coordinate} does not have a type set`, { nodes: elt.sourceAST }); this.hasMissingTypes = false; } return !!elt.type; } validateName(elt) { if ((0, introspection_1.isIntrospectionName)(elt.name)) { this.addError(`Name "${elt.name}" must not begin with "__", which is reserved by GraphQL introspection.`, elt.sourceAST ? { nodes: elt.sourceAST } : {}); return; } try { (0, graphql_1.assertName)(elt.name); } catch (e) { this.addError(e.message, elt.sourceAST ? { nodes: elt.sourceAST } : {}); } } validateObjectOrInterfaceType(type) { if (!type.hasFields()) { this.addError(`Type ${type.name} must define one or more fields.`, { nodes: type.sourceAST }); } for (const field of type.fields()) { this.validateName(field); this.validateHasType(field); for (const arg of field.arguments()) { this.validateArg(arg); } } } validateImplementedInterfaces(type) { if (type.implementsInterface(type.name)) { this.addError(`Type ${type} cannot implement itself because it would create a circular reference.`, { nodes: (0, definitions_1.sourceASTs)(type, type.interfaceImplementation(type.name)) }); } for (const itf of type.interfaces()) { for (const itfField of itf.fields()) { const field = type.field(itfField.name); if (!field) { this.addError(`Interface field ${itfField.coordinate} expected but ${type} does not provide it.`, { nodes: (0, definitions_1.sourceASTs)(itfField, type) }); continue; } if (this.validateHasType(itfField) && !(0, types_1.isSubtype)(itfField.type, field.type)) { this.addError(`Interface field ${itfField.coordinate} expects type ${itfField.type} but ${field.coordinate} of type ${field.type} is not a proper subtype.`, { nodes: (0, definitions_1.sourceASTs)(itfField, field) }); } for (const itfArg of itfField.arguments()) { const arg = field.argument(itfArg.name); if (!arg) { this.addError(`Interface field argument ${itfArg.coordinate} expected but ${field.coordinate} does not provide it.`, { nodes: (0, definitions_1.sourceASTs)(itfArg, field) }); continue; } if (this.validateHasType(itfArg) && !(0, types_1.sameType)(itfArg.type, arg.type)) { this.addError(`Interface field argument ${itfArg.coordinate} expects type ${itfArg.type} but ${arg.coordinate} is type ${arg.type}.`, { nodes: (0, definitions_1.sourceASTs)(itfArg, arg) }); } } for (const arg of field.arguments()) { if (itfField.argument(arg.name)) { continue; } if (arg.isRequired()) { this.addError(`Field ${field.coordinate} includes required argument ${arg.name} that is missing from the Interface field ${itfField.coordinate}.`, { nodes: (0, definitions_1.sourceASTs)(arg, itfField) }); } } } for (const itfOfItf of itf.interfaces()) { if (!type.implementsInterface(itfOfItf)) { if (itfOfItf === type) { this.addError(`Type ${type} cannot implement ${itf} because it would create a circular reference.`, { nodes: (0, definitions_1.sourceASTs)(type, itf) }); } else { this.addError(`Type ${type} must implement ${itfOfItf} because it is implemented by ${itf}.`, { nodes: (0, definitions_1.sourceASTs)(type, itf, itfOfItf) }); } } } } } validateInputObjectType(type) { if (!type.hasFields()) { this.addError(`Input Object type ${type.name} must define one or more fields.`, { nodes: type.sourceAST }); } for (const field of type.fields()) { this.validateName(field); if (!this.validateHasType(field)) { continue; } if (field.isRequired() && field.isDeprecated()) { this.addError(`Required input field ${field.coordinate} cannot be deprecated.`, { nodes: (0, definitions_1.sourceASTs)(field.appliedDirectivesOf('deprecated')[0], field) }); } if (field.defaultValue !== undefined && !(0, values_1.isValidValue)(field.defaultValue, field, new definitions_1.VariableDefinitions())) { this.addError(`Invalid default value (got: ${(0, values_1.valueToString)(field.defaultValue)}) provided for input field ${field.coordinate} of type ${field.type}.`, { nodes: (0, definitions_1.sourceASTs)(field) }); } } } validateArg(arg) { this.validateName(arg); if (!this.validateHasType(arg)) { return; } if (arg.isRequired() && arg.isDeprecated()) { this.addError(`Required argument ${arg.coordinate} cannot be deprecated.`, { nodes: (0, definitions_1.sourceASTs)(arg.appliedDirectivesOf('deprecated')[0], arg) }); } if (arg.defaultValue !== undefined && !(0, values_1.isValidValue)(arg.defaultValue, arg, new definitions_1.VariableDefinitions())) { const builtInScalar = this.schema.builtInScalarTypes().find((t) => arg.type && (0, definitions_1.isScalarType)(arg.type) && t.name === arg.type.name); if (!builtInScalar || !(0, values_1.isValidValueApplication)(arg.defaultValue, builtInScalar, arg.defaultValue, new definitions_1.VariableDefinitions())) { this.addError(`Invalid default value (got: ${(0, values_1.valueToString)(arg.defaultValue)}) provided for argument ${arg.coordinate} of type ${arg.type}.`, { nodes: (0, definitions_1.sourceASTs)(arg) }); } } } validateUnionType(type) { if (type.membersCount() === 0) { this.addError(`Union type ${type.coordinate} must define one or more member types.`, { nodes: type.sourceAST }); } } validateEnumType(type) { if (type.values.length === 0) { this.addError(`Enum type ${type.coordinate} must define one or more values.`, { nodes: type.sourceAST }); } for (const value of type.values) { this.validateName(value); if (value.name === 'true' || value.name === 'false' || value.name === 'null') { this.addError(`Enum type ${type.coordinate} cannot include value: ${value}.`, { nodes: value.sourceAST }); } } } validateDirectiveApplication(definition, application) { for (const argument of definition.arguments()) { const value = application.arguments()[argument.name]; if (!value) { continue; } if (argument.type && !(0, values_1.isValidValue)(value, argument, this.emptyVariables)) { const parent = application.parent; const parentDesc = parent instanceof definitions_1.NamedSchemaElement ? parent.coordinate : 'schema'; this.addError(`Invalid value for "${argument.coordinate}" of type "${argument.type}" in application of "${definition.coordinate}" to "${parentDesc}".`, { nodes: (0, definitions_1.sourceASTs)(application, argument) }); } } } } //# sourceMappingURL=validate.js.map