@apollo/federation-internals
Version:
Apollo Federation internal utilities
242 lines • 11.9 kB
JavaScript
"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