@graphql-tools/executor
Version:
Fork of GraphQL.js' execute function
141 lines (140 loc) • 6.28 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getVariableValues = getVariableValues;
const graphql_1 = require("graphql");
const utils_1 = require("@graphql-tools/utils");
const validateInputValue_js_1 = require("./validateInputValue.js");
/**
* Prepares an object map of variableValues of the correct type based on the
* provided variable definitions and arbitrary input. If the input cannot be
* parsed to match the variable definitions, a GraphQLError will be thrown.
*
* Note: The returned value is a plain Object with a prototype, since it is
* exposed to user code. Care should be taken to not pull values from the
* Object prototype.
*/
function getVariableValues(schema, varDefNodes, inputs, options) {
const errors = [];
const maxErrors = options?.maxErrors;
const hideSuggestions = options?.hideSuggestions;
try {
const variableValues = coerceVariableValues(schema, varDefNodes, inputs, error => {
if (maxErrors != null && errors.length >= maxErrors) {
throw (0, utils_1.createGraphQLError)('Too many errors processing variables, error limit reached. Execution aborted.');
}
errors.push(error);
}, hideSuggestions);
if (errors.length === 0) {
return { variableValues };
}
}
catch (error) {
errors.push(error);
}
return { errors };
}
function coerceVariableValues(schema, varDefNodes, inputs, onError, hideSuggestions) {
const coercedValues = {};
const sources = {};
for (const varDefNode of varDefNodes) {
const varName = varDefNode.variable.name.value;
const varType = (0, graphql_1.typeFromAST)(schema, varDefNode.type);
if (!(0, graphql_1.isInputType)(varType)) {
// Must use input types for variables. This should be caught during
// validation, however is checked again here for safety.
const varTypeStr = (0, graphql_1.print)(varDefNode.type);
onError((0, utils_1.createGraphQLError)(`Variable "$${varName}" expected value of type "${varTypeStr}" which cannot be used as an input type.`, { nodes: varDefNode.type }));
continue;
}
const signature = {
name: varName,
type: varType,
default: varDefNode.defaultValue == null ? undefined : { literal: varDefNode.defaultValue },
};
if (!(0, utils_1.hasOwnProperty)(inputs, varName)) {
sources[varName] = { signature };
if (varDefNode.defaultValue) {
coercedValues[varName] = (0, graphql_1.valueFromAST)(varDefNode.defaultValue, varType);
}
else if ((0, graphql_1.isNonNullType)(varType)) {
const varTypeStr = (0, utils_1.inspect)(varType);
onError((0, utils_1.createGraphQLError)(`Variable "$${varName}" of required type "${varTypeStr}" was not provided.`, {
nodes: varDefNode,
}));
}
continue;
}
const value = inputs[varName];
const sourceValue = snapshotInputValue(value, varType);
sources[varName] = { signature, value: sourceValue };
if (value === null && (0, graphql_1.isNonNullType)(varType)) {
const varTypeStr = (0, utils_1.inspect)(varType);
onError((0, utils_1.createGraphQLError)(`Variable "$${varName}" of non-null type "${varTypeStr}" must not be null.`, {
nodes: varDefNode,
}));
continue;
}
let errored = false;
const callback = (path, invalidValue, error) => {
errored = true;
let prefix = `Variable "$${varName}" got invalid value ` + (0, utils_1.inspect)(invalidValue);
if (path.length > 0) {
prefix += ` at "${varName}${(0, utils_1.printPathArray)(path)}"`;
}
onError((0, utils_1.createGraphQLError)(prefix + '; ' + error.message, {
nodes: varDefNode,
originalError: error,
}));
};
coercedValues[varName] = graphql_1.coerceInputValue(sourceValue, varType, callback);
if (coercedValues[varName] === undefined && !errored) {
(0, validateInputValue_js_1.validateInputValue)(sourceValue, varType, callback, hideSuggestions);
}
}
return {
coerced: coercedValues,
sources,
};
}
function snapshotInputValue(value, type) {
if ((0, graphql_1.isNonNullType)(type)) {
return snapshotInputValue(value, type.ofType);
}
if (value == null) {
return value;
}
if ((0, graphql_1.isListType)(type)) {
if (!isIterableInput(value)) {
return snapshotInputValue(value, type.ofType);
}
return Array.from(value, item => snapshotInputValue(item, type.ofType));
}
// Arrays are objects in JS; do not spread them into `{}` or invalid list
// inputs (e.g. `[]` for an input object) would be accepted after defaults.
// Snapshot into a null-prototype object so Object.prototype keys (e.g.
// `toString`) are not treated as own fields, while still reading declared
// fields with normal property access (prototype getters stay visible).
if ((0, graphql_1.isInputObjectType)(type) && typeof value === 'object' && !Array.isArray(value)) {
const obj = value;
const snapshot = Object.create(null);
for (const key of Object.keys(obj)) {
snapshot[key] = obj[key];
}
for (const field of Object.values(type.getFields())) {
const fieldValue = obj[field.name];
if (fieldValue !== undefined) {
snapshot[field.name] = snapshotInputValue(fieldValue, field.type);
}
}
return snapshot;
}
return value;
}
function isIterableInput(value) {
// Match graphql-js: only call Array.from when the iterator is callable.
// `Symbol.iterator in value` is true for `{ [Symbol.iterator]: null }`, which
// would otherwise snapshot to `[]` instead of a singleton list item.
return (value != null &&
typeof value === 'object' &&
typeof value[Symbol.iterator] === 'function');
}