VariablesInAllowedPositionRule.js.flow 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. // @flow strict
  2. import inspect from '../../jsutils/inspect';
  3. import { GraphQLError } from '../../error/GraphQLError';
  4. import { Kind } from '../../language/kinds';
  5. import type { ValueNode } from '../../language/ast';
  6. import type { ASTVisitor } from '../../language/visitor';
  7. import type { GraphQLSchema } from '../../type/schema';
  8. import type { GraphQLType } from '../../type/definition';
  9. import { isNonNullType } from '../../type/definition';
  10. import { typeFromAST } from '../../utilities/typeFromAST';
  11. import { isTypeSubTypeOf } from '../../utilities/typeComparators';
  12. import type { ValidationContext } from '../ValidationContext';
  13. /**
  14. * Variables passed to field arguments conform to type
  15. */
  16. export function VariablesInAllowedPositionRule(
  17. context: ValidationContext,
  18. ): ASTVisitor {
  19. let varDefMap = Object.create(null);
  20. return {
  21. OperationDefinition: {
  22. enter() {
  23. varDefMap = Object.create(null);
  24. },
  25. leave(operation) {
  26. const usages = context.getRecursiveVariableUsages(operation);
  27. for (const { node, type, defaultValue } of usages) {
  28. const varName = node.name.value;
  29. const varDef = varDefMap[varName];
  30. if (varDef && type) {
  31. // A var type is allowed if it is the same or more strict (e.g. is
  32. // a subtype of) than the expected type. It can be more strict if
  33. // the variable type is non-null when the expected type is nullable.
  34. // If both are list types, the variable item type can be more strict
  35. // than the expected item type (contravariant).
  36. const schema = context.getSchema();
  37. const varType = typeFromAST(schema, varDef.type);
  38. if (
  39. varType &&
  40. !allowedVariableUsage(
  41. schema,
  42. varType,
  43. varDef.defaultValue,
  44. type,
  45. defaultValue,
  46. )
  47. ) {
  48. const varTypeStr = inspect(varType);
  49. const typeStr = inspect(type);
  50. context.reportError(
  51. new GraphQLError(
  52. `Variable "$${varName}" of type "${varTypeStr}" used in position expecting type "${typeStr}".`,
  53. [varDef, node],
  54. ),
  55. );
  56. }
  57. }
  58. }
  59. },
  60. },
  61. VariableDefinition(node) {
  62. varDefMap[node.variable.name.value] = node;
  63. },
  64. };
  65. }
  66. /**
  67. * Returns true if the variable is allowed in the location it was found,
  68. * which includes considering if default values exist for either the variable
  69. * or the location at which it is located.
  70. */
  71. function allowedVariableUsage(
  72. schema: GraphQLSchema,
  73. varType: GraphQLType,
  74. varDefaultValue: ?ValueNode,
  75. locationType: GraphQLType,
  76. locationDefaultValue: ?mixed,
  77. ): boolean {
  78. if (isNonNullType(locationType) && !isNonNullType(varType)) {
  79. const hasNonNullVariableDefaultValue =
  80. varDefaultValue != null && varDefaultValue.kind !== Kind.NULL;
  81. const hasLocationDefaultValue = locationDefaultValue !== undefined;
  82. if (!hasNonNullVariableDefaultValue && !hasLocationDefaultValue) {
  83. return false;
  84. }
  85. const nullableLocationType = locationType.ofType;
  86. return isTypeSubTypeOf(schema, varType, nullableLocationType);
  87. }
  88. return isTypeSubTypeOf(schema, varType, locationType);
  89. }