VariablesInAllowedPositionRule.mjs 3.1 KB

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