VariablesInAllowedPosition.js.flow 3.3 KB

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