import { inspect } from '../jsutils/inspect.js';
import { keyMap } from '../jsutils/keyMap.js';
import type { Maybe } from '../jsutils/Maybe.js';
import type { ObjMap } from '../jsutils/ObjMap.js';
import { printPathArray } from '../jsutils/printPathArray.js';
import { GraphQLError } from '../error/GraphQLError.js';
import type {
DirectiveNode,
FieldNode,
VariableDefinitionNode,
} from '../language/ast.js';
import { Kind } from '../language/kinds.js';
import { print } from '../language/printer.js';
import type { GraphQLField } from '../type/definition.js';
import { isInputType, isNonNullType } from '../type/definition.js';
import type { GraphQLDirective } from '../type/directives.js';
import type { GraphQLSchema } from '../type/schema.js';
import { coerceInputValue } from '../utilities/coerceInputValue.js';
import { typeFromAST } from '../utilities/typeFromAST.js';
import { valueFromAST } from '../utilities/valueFromAST.js';
type CoercedVariableValues =
| { errors: ReadonlyArray<GraphQLError>; coerced?: never }
| { coerced: { [variable: string]: unknown }; errors?: never };
export function getVariableValues(
schema: GraphQLSchema,
varDefNodes: ReadonlyArray<VariableDefinitionNode>,
inputs: { readonly [variable: string]: unknown },
options?: { maxErrors?: number },
): CoercedVariableValues {
const errors = [];
const maxErrors = options?.maxErrors;
try {
const coerced = coerceVariableValues(
schema,
varDefNodes,
inputs,
(error) => {
if (maxErrors != null && errors.length >= maxErrors) {
throw new GraphQLError(
'Too many errors processing variables, error limit reached. Execution aborted.',
);
}
errors.push(error);
},
);
if (errors.length === 0) {
return { coerced };
}
} catch (error) {
errors.push(error);
}
return { errors };
}
function coerceVariableValues(
schema: GraphQLSchema,
varDefNodes: ReadonlyArray<VariableDefinitionNode>,
inputs: { readonly [variable: string]: unknown },
onError: (error: GraphQLError) => void,
): { [variable: string]: unknown } {
const coercedValues: { [variable: string]: unknown } = {};
for (const varDefNode of varDefNodes) {
const varName = varDefNode.variable.name.value;
const varType = typeFromAST(schema, varDefNode.type);
if (!isInputType(varType)) {
const varTypeStr = print(varDefNode.type);
onError(
new GraphQLError(
`Variable "$${varName}" expected value of type "${varTypeStr}" which cannot be used as an input type.`,
{ nodes: varDefNode.type },
),
);
continue;
}
if (!Object.hasOwn(inputs, varName)) {
if (varDefNode.defaultValue) {
coercedValues[varName] = valueFromAST(varDefNode.defaultValue, varType);
} else if (isNonNullType(varType)) {
const varTypeStr = inspect(varType);
onError(
new GraphQLError(
`Variable "$${varName}" of required type "${varTypeStr}" was not provided.`,
{ nodes: varDefNode },
),
);
}
continue;
}
const value = inputs[varName];
if (value === null && isNonNullType(varType)) {
const varTypeStr = inspect(varType);
onError(
new GraphQLError(
`Variable "$${varName}" of non-null type "${varTypeStr}" must not be null.`,
{ nodes: varDefNode },
),
);
continue;
}
coercedValues[varName] = coerceInputValue(
value,
varType,
(path, invalidValue, error) => {
let prefix =
`Variable "$${varName}" got invalid value ` + inspect(invalidValue);
if (path.length > 0) {
prefix += ` at "${varName}${printPathArray(path)}"`;
}
onError(
new GraphQLError(prefix + '; ' + error.message, {
nodes: varDefNode,
originalError: error,
}),
);
},
);
}
return coercedValues;
}
export function getArgumentValues(
def: GraphQLField<unknown, unknown> | GraphQLDirective,
node: FieldNode | DirectiveNode,
variableValues?: Maybe<ObjMap<unknown>>,
): { [argument: string]: unknown } {
const coercedValues: { [argument: string]: unknown } = {};
const argumentNodes = node.arguments ?? [];
const argNodeMap = keyMap(argumentNodes, (arg) => arg.name.value);
for (const argDef of def.args) {
const name = argDef.name;
const argType = argDef.type;
const argumentNode = argNodeMap[name];
if (!argumentNode) {
if (argDef.defaultValue !== undefined) {
coercedValues[name] = argDef.defaultValue;
} else if (isNonNullType(argType)) {
throw new GraphQLError(
`Argument "${name}" of required type "${inspect(argType)}" ` +
'was not provided.',
{ nodes: node },
);
}
continue;
}
const valueNode = argumentNode.value;
let isNull = valueNode.kind === Kind.NULL;
if (valueNode.kind === Kind.VARIABLE) {
const variableName = valueNode.name.value;
if (
variableValues == null ||
!Object.hasOwn(variableValues, variableName)
) {
if (argDef.defaultValue !== undefined) {
coercedValues[name] = argDef.defaultValue;
} else if (isNonNullType(argType)) {
throw new GraphQLError(
`Argument "${name}" of required type "${inspect(argType)}" ` +
`was provided the variable "$${variableName}" which was not provided a runtime value.`,
{ nodes: valueNode },
);
}
continue;
}
isNull = variableValues[variableName] == null;
}
if (isNull && isNonNullType(argType)) {
throw new GraphQLError(
`Argument "${name}" of non-null type "${inspect(argType)}" ` +
'must not be null.',
{ nodes: valueNode },
);
}
const coercedValue = valueFromAST(valueNode, argType, variableValues);
if (coercedValue === undefined) {
throw new GraphQLError(
`Argument "${name}" has invalid value ${print(valueNode)}.`,
{ nodes: valueNode },
);
}
coercedValues[name] = coercedValue;
}
return coercedValues;
}
export function getDirectiveValues(
directiveDef: GraphQLDirective,
node: { readonly directives?: ReadonlyArray<DirectiveNode> | undefined },
variableValues?: Maybe<ObjMap<unknown>>,
): undefined | { [argument: string]: unknown } {
const directiveNode = node.directives?.find(
(directive) => directive.name.value === directiveDef.name,
);
if (directiveNode) {
return getArgumentValues(directiveDef, directiveNode, variableValues);
}
}