UNPKG

@graphql-tools/executor

Version:

Fork of GraphQL.js' execute function

138 lines (137 loc) • 6 kB
import { coerceInputValue, isInputObjectType, isInputType, isListType, isNonNullType, print, typeFromAST, valueFromAST, } from 'graphql'; import { createGraphQLError, hasOwnProperty, inspect, printPathArray, } from '@graphql-tools/utils'; import { validateInputValue } from './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. */ export 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 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 = typeFromAST(schema, varDefNode.type); if (!isInputType(varType)) { // Must use input types for variables. This should be caught during // validation, however is checked again here for safety. const varTypeStr = print(varDefNode.type); onError(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 (!hasOwnProperty(inputs, varName)) { sources[varName] = { signature }; if (varDefNode.defaultValue) { coercedValues[varName] = valueFromAST(varDefNode.defaultValue, varType); } else if (isNonNullType(varType)) { const varTypeStr = inspect(varType); onError(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 && isNonNullType(varType)) { const varTypeStr = inspect(varType); onError(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 ` + inspect(invalidValue); if (path.length > 0) { prefix += ` at "${varName}${printPathArray(path)}"`; } onError(createGraphQLError(prefix + '; ' + error.message, { nodes: varDefNode, originalError: error, })); }; coercedValues[varName] = coerceInputValue(sourceValue, varType, callback); if (coercedValues[varName] === undefined && !errored) { validateInputValue(sourceValue, varType, callback, hideSuggestions); } } return { coerced: coercedValues, sources, }; } function snapshotInputValue(value, type) { if (isNonNullType(type)) { return snapshotInputValue(value, type.ofType); } if (value == null) { return value; } if (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 (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'); }