@envelop/extended-validation
Version:
72 lines (71 loc) • 3.31 kB
JavaScript
import { getNamedType, GraphQLInputObjectType, GraphQLNonNull, isListType } from 'graphql';
import { createGraphQLError, getArgumentValues } from '@graphql-tools/utils';
import { getDirectiveFromAstNode } from '../common.js';
export const ONE_OF_DIRECTIVE_SDL = /* GraphQL */ `
directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
`;
export const OneOfInputObjectsRule = (validationContext, executionArgs) => {
return {
Field: node => {
if (node.arguments?.length) {
const fieldType = validationContext.getFieldDef();
if (!fieldType) {
return;
}
const values = getArgumentValues(fieldType, node, executionArgs.variableValues || undefined);
const isOneOfFieldType = fieldType.extensions?.['oneOf'] ||
(fieldType.astNode && getDirectiveFromAstNode(fieldType.astNode, 'oneOf'));
if (isOneOfFieldType && Object.keys(values).length !== 1) {
validationContext.reportError(createGraphQLError(`Exactly one key must be specified for input for field "${fieldType.type.toString()}.${node.name.value}"`, { nodes: [node] }));
}
for (const arg of node.arguments) {
const argType = fieldType.args.find(typeArg => typeArg.name === arg.name.value);
if (argType) {
traverseVariables(validationContext, arg, argType.type, values[arg.name.value]);
}
}
}
},
};
};
function getNonNullType(ttype) {
if (ttype instanceof GraphQLNonNull) {
return ttype.ofType;
}
return ttype;
}
function traverseVariables(validationContext, arg, graphqlType, currentValue) {
// if the current value is empty we don't need to traverse deeper
// if it shouldn't be empty, the "original" validation phase should complain.
if (currentValue == null) {
return;
}
const unwrappedType = getNonNullType(graphqlType);
if (isListType(unwrappedType)) {
if (!Array.isArray(currentValue)) {
// because of graphql type coercion a single object should be treated as an array of one object
currentValue = [currentValue];
}
for (const value of currentValue) {
traverseVariables(validationContext, arg, unwrappedType.ofType, value);
}
return;
}
if (typeof currentValue !== 'object') {
// in case the value is not an object, the "original" validation phase should complain.
return;
}
const inputType = getNamedType(graphqlType);
const isOneOfInputType = inputType.extensions?.['oneOf'] ||
(inputType.astNode && getDirectiveFromAstNode(inputType.astNode, 'oneOf'));
if (isOneOfInputType && Object.keys(currentValue).length !== 1) {
validationContext.reportError(createGraphQLError(`OneOf Input Object "${inputType.name}" must specify exactly one key.`, {
nodes: [arg],
}));
}
if (inputType instanceof GraphQLInputObjectType) {
for (const [name, fieldConfig] of Object.entries(inputType.getFields())) {
traverseVariables(validationContext, arg, fieldConfig.type, currentValue[name]);
}
}
}