| import { didYouMean } from '../../jsutils/didYouMean.mjs'; |
| import { inspect } from '../../jsutils/inspect.mjs'; |
| import { keyMap } from '../../jsutils/keyMap.mjs'; |
| import { suggestionList } from '../../jsutils/suggestionList.mjs'; |
| import { GraphQLError } from '../../error/GraphQLError.mjs'; |
| import { Kind } from '../../language/kinds.mjs'; |
| import { print } from '../../language/printer.mjs'; |
| import { |
| getNamedType, |
| getNullableType, |
| isInputObjectType, |
| isLeafType, |
| isListType, |
| isNonNullType, |
| isRequiredInputField, |
| } from '../../type/definition.mjs'; |
|
|
| /** |
| * Value literals of correct type |
| * |
| * A GraphQL document is only valid if all value literals are of the type |
| * expected at their position. |
| * |
| * See https://spec.graphql.org/draft/#sec-Values-of-Correct-Type |
| */ |
| export function ValuesOfCorrectTypeRule(context) { |
| let variableDefinitions = {}; |
| return { |
| OperationDefinition: { |
| enter() { |
| variableDefinitions = {}; |
| }, |
| }, |
|
|
| VariableDefinition(definition) { |
| variableDefinitions[definition.variable.name.value] = definition; |
| }, |
|
|
| ListValue(node) { |
| // Note: TypeInfo will traverse into a list's item type, so look to the |
| // parent input type to check if it is a list. |
| const type = getNullableType(context.getParentInputType()); |
|
|
| if (!isListType(type)) { |
| isValidValueNode(context, node); |
| return false; // Don't traverse further. |
| } |
| }, |
|
|
| ObjectValue(node) { |
| const type = getNamedType(context.getInputType()); |
|
|
| if (!isInputObjectType(type)) { |
| isValidValueNode(context, node); |
| return false; // Don't traverse further. |
| } // Ensure every required field exists. |
|
|
| const fieldNodeMap = keyMap(node.fields, (field) => field.name.value); |
|
|
| for (const fieldDef of Object.values(type.getFields())) { |
| const fieldNode = fieldNodeMap[fieldDef.name]; |
|
|
| if (!fieldNode && isRequiredInputField(fieldDef)) { |
| const typeStr = inspect(fieldDef.type); |
| context.reportError( |
| new GraphQLError( |
| `Field "${type.name}.${fieldDef.name}" of required type "${typeStr}" was not provided.`, |
| { |
| nodes: node, |
| }, |
| ), |
| ); |
| } |
| } |
|
|
| if (type.isOneOf) { |
| validateOneOfInputObject( |
| context, |
| node, |
| type, |
| fieldNodeMap, |
| variableDefinitions, |
| ); |
| } |
| }, |
|
|
| ObjectField(node) { |
| const parentType = getNamedType(context.getParentInputType()); |
| const fieldType = context.getInputType(); |
|
|
| if (!fieldType && isInputObjectType(parentType)) { |
| const suggestions = suggestionList( |
| node.name.value, |
| Object.keys(parentType.getFields()), |
| ); |
| context.reportError( |
| new GraphQLError( |
| `Field "${node.name.value}" is not defined by type "${parentType.name}".` + |
| didYouMean(suggestions), |
| { |
| nodes: node, |
| }, |
| ), |
| ); |
| } |
| }, |
|
|
| NullValue(node) { |
| const type = context.getInputType(); |
|
|
| if (isNonNullType(type)) { |
| context.reportError( |
| new GraphQLError( |
| `Expected value of type "${inspect(type)}", found ${print(node)}.`, |
| { |
| nodes: node, |
| }, |
| ), |
| ); |
| } |
| }, |
|
|
| EnumValue: (node) => isValidValueNode(context, node), |
| IntValue: (node) => isValidValueNode(context, node), |
| FloatValue: (node) => isValidValueNode(context, node), |
| StringValue: (node) => isValidValueNode(context, node), |
| BooleanValue: (node) => isValidValueNode(context, node), |
| }; |
| } |
| /** |
| * Any value literal may be a valid representation of a Scalar, depending on |
| * that scalar type. |
| */ |
|
|
| function isValidValueNode(context, node) { |
| // Report any error at the full type expected by the location. |
| const locationType = context.getInputType(); |
|
|
| if (!locationType) { |
| return; |
| } |
|
|
| const type = getNamedType(locationType); |
|
|
| if (!isLeafType(type)) { |
| const typeStr = inspect(locationType); |
| context.reportError( |
| new GraphQLError( |
| `Expected value of type "${typeStr}", found ${print(node)}.`, |
| { |
| nodes: node, |
| }, |
| ), |
| ); |
| return; |
| } // Scalars and Enums determine if a literal value is valid via parseLiteral(), |
| // which may throw or return an invalid value to indicate failure. |
|
|
| try { |
| const parseResult = type.parseLiteral( |
| node, |
| undefined, |
| /* variables */ |
| ); |
|
|
| if (parseResult === undefined) { |
| const typeStr = inspect(locationType); |
| context.reportError( |
| new GraphQLError( |
| `Expected value of type "${typeStr}", found ${print(node)}.`, |
| { |
| nodes: node, |
| }, |
| ), |
| ); |
| } |
| } catch (error) { |
| const typeStr = inspect(locationType); |
|
|
| if (error instanceof GraphQLError) { |
| context.reportError(error); |
| } else { |
| context.reportError( |
| new GraphQLError( |
| `Expected value of type "${typeStr}", found ${print(node)}; ` + |
| error.message, |
| { |
| nodes: node, |
| originalError: error, |
| }, |
| ), |
| ); |
| } |
| } |
| } |
|
|
| function validateOneOfInputObject( |
| context, |
| node, |
| type, |
| fieldNodeMap, |
| variableDefinitions, |
| ) { |
| var _fieldNodeMap$keys$; |
|
|
| const keys = Object.keys(fieldNodeMap); |
| const isNotExactlyOneField = keys.length !== 1; |
|
|
| if (isNotExactlyOneField) { |
| context.reportError( |
| new GraphQLError( |
| `OneOf Input Object "${type.name}" must specify exactly one key.`, |
| { |
| nodes: [node], |
| }, |
| ), |
| ); |
| return; |
| } |
|
|
| const value = |
| (_fieldNodeMap$keys$ = fieldNodeMap[keys[0]]) === null || |
| _fieldNodeMap$keys$ === void 0 |
| ? void 0 |
| : _fieldNodeMap$keys$.value; |
| const isNullLiteral = !value || value.kind === Kind.NULL; |
| const isVariable = |
| (value === null || value === void 0 ? void 0 : value.kind) === |
| Kind.VARIABLE; |
|
|
| if (isNullLiteral) { |
| context.reportError( |
| new GraphQLError(`Field "${type.name}.${keys[0]}" must be non-null.`, { |
| nodes: [node], |
| }), |
| ); |
| return; |
| } |
|
|
| if (isVariable) { |
| const variableName = value.name.value; |
| const definition = variableDefinitions[variableName]; |
| const isNullableVariable = definition.type.kind !== Kind.NON_NULL_TYPE; |
|
|
| if (isNullableVariable) { |
| context.reportError( |
| new GraphQLError( |
| `Variable "${variableName}" must be non-nullable to be used for OneOf Input Object "${type.name}".`, |
| { |
| nodes: [node], |
| }, |
| ), |
| ); |
| } |
| } |
| } |