UNPKG

@graphql-tools/stitch

Version:

A set of utils for faster development of GraphQL tools

145 lines (144 loc) • 10.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateInputEnumConsistency = exports.validateTypeConsistency = exports.validateInputFieldConsistency = exports.validateInputObjectConsistency = exports.validateFieldConsistency = void 0; const graphql_1 = require("graphql"); const types_js_1 = require("./types.js"); function validateFieldConsistency(finalFieldConfig, candidates, typeMergingOptions) { const fieldNamespace = `${candidates[0].type.name}.${candidates[0].fieldName}`; const finalFieldNull = (0, graphql_1.isNonNullType)(finalFieldConfig.type); validateTypeConsistency(finalFieldConfig, candidates.map(c => c.fieldConfig), 'field', fieldNamespace, typeMergingOptions); if (getValidationSettings(fieldNamespace, typeMergingOptions).strictNullComparison && candidates.some(c => finalFieldNull !== (0, graphql_1.isNonNullType)(c.fieldConfig.type))) { validationMessage(`Nullability of field "${fieldNamespace}" does not match across subschemas. Disable typeMergingOptions.validationSettings.strictNullComparison to permit safe divergences.`, fieldNamespace, typeMergingOptions); } else if (finalFieldNull && candidates.some(c => !(0, graphql_1.isNonNullType)(c.fieldConfig.type))) { validationMessage(`Canonical definition of field "${fieldNamespace}" is not-null while some subschemas permit null. This will be an automatic error in future versions.`, fieldNamespace, typeMergingOptions); } const argCandidatesMap = Object.create(null); for (const { fieldConfig } of candidates) { if (fieldConfig.args == null) { continue; } for (const argName in fieldConfig.args) { const arg = fieldConfig.args[argName]; argCandidatesMap[argName] = argCandidatesMap[argName] || []; argCandidatesMap[argName].push(arg); } } if (Object.values(argCandidatesMap).some(argCandidates => candidates.length !== argCandidates.length)) { validationMessage(`Canonical definition of field "${fieldNamespace}" implements inconsistent argument names across subschemas. Input may be filtered from some requests.`, fieldNamespace, typeMergingOptions); } for (const argName in argCandidatesMap) { if (finalFieldConfig.args == null) { continue; } const argCandidates = argCandidatesMap[argName]; const argNamespace = `${fieldNamespace}.${argName}`; const finalArgConfig = finalFieldConfig.args[argName] || argCandidates[argCandidates.length - 1]; const finalArgType = (0, graphql_1.getNamedType)(finalArgConfig.type); const finalArgNull = (0, graphql_1.isNonNullType)(finalArgConfig.type); validateTypeConsistency(finalArgConfig, argCandidates, 'argument', argNamespace, typeMergingOptions); if (getValidationSettings(argNamespace, typeMergingOptions).strictNullComparison && argCandidates.some(c => finalArgNull !== (0, graphql_1.isNonNullType)(c.type))) { validationMessage(`Nullability of argument "${argNamespace}" does not match across subschemas. Disable typeMergingOptions.validationSettings.strictNullComparison to permit safe divergences.`, argNamespace, typeMergingOptions); } else if (!finalArgNull && argCandidates.some(c => (0, graphql_1.isNonNullType)(c.type))) { validationMessage(`Canonical definition of argument "${argNamespace}" permits null while some subschemas require not-null. This will be an automatic error in future versions.`, argNamespace, typeMergingOptions); } if ((0, graphql_1.isEnumType)(finalArgType)) { validateInputEnumConsistency(finalArgType, argCandidates, typeMergingOptions); } } } exports.validateFieldConsistency = validateFieldConsistency; function validateInputObjectConsistency(fieldInclusionMap, candidates, typeMergingOptions) { for (const fieldName in fieldInclusionMap) { const count = fieldInclusionMap[fieldName]; if (candidates.length !== count) { const namespace = `${candidates[0].type.name}.${fieldName}`; validationMessage(`Definition of input field "${namespace}" is not implemented by all subschemas. Input may be filtered from some requests.`, namespace, typeMergingOptions); } } } exports.validateInputObjectConsistency = validateInputObjectConsistency; function validateInputFieldConsistency(finalInputFieldConfig, candidates, typeMergingOptions) { const inputFieldNamespace = `${candidates[0].type.name}.${candidates[0].fieldName}`; const inputFieldConfigs = candidates.map(c => c.inputFieldConfig); const finalInputFieldType = (0, graphql_1.getNamedType)(finalInputFieldConfig.type); const finalInputFieldNull = (0, graphql_1.isNonNullType)(finalInputFieldConfig.type); validateTypeConsistency(finalInputFieldConfig, inputFieldConfigs, 'input field', inputFieldNamespace, typeMergingOptions); if (getValidationSettings(inputFieldNamespace, typeMergingOptions).strictNullComparison && candidates.some(c => finalInputFieldNull !== (0, graphql_1.isNonNullType)(c.inputFieldConfig.type))) { validationMessage(`Nullability of input field "${inputFieldNamespace}" does not match across subschemas. Disable typeMergingOptions.validationSettings.strictNullComparison to permit safe divergences.`, inputFieldNamespace, typeMergingOptions); } else if (!finalInputFieldNull && candidates.some(c => (0, graphql_1.isNonNullType)(c.inputFieldConfig.type))) { validationMessage(`Canonical definition of input field "${inputFieldNamespace}" permits null while some subschemas require not-null. This will be an automatic error in future versions.`, inputFieldNamespace, typeMergingOptions); } if ((0, graphql_1.isEnumType)(finalInputFieldType)) { validateInputEnumConsistency(finalInputFieldType, inputFieldConfigs, typeMergingOptions); } } exports.validateInputFieldConsistency = validateInputFieldConsistency; function validateTypeConsistency(finalElementConfig, candidates, definitionType, settingNamespace, typeMergingOptions) { var _a, _b, _c; const finalNamedType = (0, graphql_1.getNamedType)(finalElementConfig.type); const finalIsScalar = (0, graphql_1.isScalarType)(finalNamedType); const finalIsList = hasListType(finalElementConfig.type); for (const c of candidates) { if (finalIsList !== hasListType(c.type)) { throw new Error(`Definitions of ${definitionType} "${settingNamespace}" implement inconsistent list types across subschemas and cannot be merged.`); } const currentNamedType = (0, graphql_1.getNamedType)(c.type); if (finalNamedType.toString() !== currentNamedType.toString()) { const proxiableScalar = !!((_c = (_b = (_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.validationSettings) === null || _a === void 0 ? void 0 : _a.proxiableScalars) === null || _b === void 0 ? void 0 : _b[finalNamedType.toString()]) === null || _c === void 0 ? void 0 : _c.includes(currentNamedType.toString())); const bothScalars = finalIsScalar && (0, graphql_1.isScalarType)(currentNamedType); const permitScalar = proxiableScalar && bothScalars; if (proxiableScalar && !bothScalars) { throw new Error(`Types ${finalNamedType} and ${currentNamedType} are not proxiable scalars.`); } if (!permitScalar) { validationMessage(`Definitions of ${definitionType} "${settingNamespace}" implement inconsistent named types across subschemas. This will be an automatic error in future versions.`, settingNamespace, typeMergingOptions); } } } } exports.validateTypeConsistency = validateTypeConsistency; function hasListType(type) { return (0, graphql_1.isListType)((0, graphql_1.getNullableType)(type)); } function validateInputEnumConsistency(inputEnumType, candidates, typeMergingOptions) { const enumValueInclusionMap = Object.create(null); for (const candidate of candidates) { const enumType = (0, graphql_1.getNamedType)(candidate.type); if ((0, graphql_1.isEnumType)(enumType)) { for (const { value } of enumType.getValues()) { enumValueInclusionMap[value] = enumValueInclusionMap[value] || 0; enumValueInclusionMap[value] += 1; } } } if (Object.values(enumValueInclusionMap).some(count => candidates.length !== count)) { validationMessage(`Enum "${inputEnumType.name}" is used as an input with inconsistent values across subschemas. This will be an automatic error in future versions.`, inputEnumType.name, typeMergingOptions); } } exports.validateInputEnumConsistency = validateInputEnumConsistency; function validationMessage(message, settingNamespace, typeMergingOptions) { var _a; const override = `typeMergingOptions.validationScopes['${settingNamespace}'].validationLevel`; const settings = getValidationSettings(settingNamespace, typeMergingOptions); switch ((_a = settings.validationLevel) !== null && _a !== void 0 ? _a : types_js_1.ValidationLevel.Warn) { case types_js_1.ValidationLevel.Off: return; case types_js_1.ValidationLevel.Error: throw new Error(`${message} If this is intentional, you may disable this error by setting ${override} = "warn|off"`); default: console.warn(`${message} To disable this warning or elevate it to an error, set ${override} = "error|off"`); } } function getValidationSettings(settingNamespace, typeMergingOptions) { var _a, _b, _c; return { ...((_a = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.validationSettings) !== null && _a !== void 0 ? _a : {}), ...((_c = (_b = typeMergingOptions === null || typeMergingOptions === void 0 ? void 0 : typeMergingOptions.validationScopes) === null || _b === void 0 ? void 0 : _b[settingNamespace]) !== null && _c !== void 0 ? _c : {}), }; }