ProvidedRequiredArgumentsRule.mjs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import { inspect } from '../../jsutils/inspect.mjs';
  2. import { keyMap } from '../../jsutils/keyMap.mjs';
  3. import { GraphQLError } from '../../error/GraphQLError.mjs';
  4. import { Kind } from '../../language/kinds.mjs';
  5. import { print } from '../../language/printer.mjs';
  6. import { isRequiredArgument, isType } from '../../type/definition.mjs';
  7. import { specifiedDirectives } from '../../type/directives.mjs';
  8. /**
  9. * Provided required arguments
  10. *
  11. * A field or directive is only valid if all required (non-null without a
  12. * default value) field arguments have been provided.
  13. */
  14. export function ProvidedRequiredArgumentsRule(context) {
  15. return {
  16. // eslint-disable-next-line new-cap
  17. ...ProvidedRequiredArgumentsOnDirectivesRule(context),
  18. Field: {
  19. // Validate on leave to allow for deeper errors to appear first.
  20. leave(fieldNode) {
  21. var _fieldNode$arguments;
  22. const fieldDef = context.getFieldDef();
  23. if (!fieldDef) {
  24. return false;
  25. }
  26. const providedArgs = new Set( // FIXME: https://github.com/graphql/graphql-js/issues/2203
  27. /* c8 ignore next */
  28. (_fieldNode$arguments = fieldNode.arguments) === null ||
  29. _fieldNode$arguments === void 0
  30. ? void 0
  31. : _fieldNode$arguments.map((arg) => arg.name.value),
  32. );
  33. for (const argDef of fieldDef.args) {
  34. if (!providedArgs.has(argDef.name) && isRequiredArgument(argDef)) {
  35. const argTypeStr = inspect(argDef.type);
  36. context.reportError(
  37. new GraphQLError(
  38. `Field "${fieldDef.name}" argument "${argDef.name}" of type "${argTypeStr}" is required, but it was not provided.`,
  39. {
  40. nodes: fieldNode,
  41. },
  42. ),
  43. );
  44. }
  45. }
  46. },
  47. },
  48. };
  49. }
  50. /**
  51. * @internal
  52. */
  53. export function ProvidedRequiredArgumentsOnDirectivesRule(context) {
  54. var _schema$getDirectives;
  55. const requiredArgsMap = Object.create(null);
  56. const schema = context.getSchema();
  57. const definedDirectives =
  58. (_schema$getDirectives =
  59. schema === null || schema === void 0
  60. ? void 0
  61. : schema.getDirectives()) !== null && _schema$getDirectives !== void 0
  62. ? _schema$getDirectives
  63. : specifiedDirectives;
  64. for (const directive of definedDirectives) {
  65. requiredArgsMap[directive.name] = keyMap(
  66. directive.args.filter(isRequiredArgument),
  67. (arg) => arg.name,
  68. );
  69. }
  70. const astDefinitions = context.getDocument().definitions;
  71. for (const def of astDefinitions) {
  72. if (def.kind === Kind.DIRECTIVE_DEFINITION) {
  73. var _def$arguments;
  74. // FIXME: https://github.com/graphql/graphql-js/issues/2203
  75. /* c8 ignore next */
  76. const argNodes =
  77. (_def$arguments = def.arguments) !== null && _def$arguments !== void 0
  78. ? _def$arguments
  79. : [];
  80. requiredArgsMap[def.name.value] = keyMap(
  81. argNodes.filter(isRequiredArgumentNode),
  82. (arg) => arg.name.value,
  83. );
  84. }
  85. }
  86. return {
  87. Directive: {
  88. // Validate on leave to allow for deeper errors to appear first.
  89. leave(directiveNode) {
  90. const directiveName = directiveNode.name.value;
  91. const requiredArgs = requiredArgsMap[directiveName];
  92. if (requiredArgs) {
  93. var _directiveNode$argume;
  94. // FIXME: https://github.com/graphql/graphql-js/issues/2203
  95. /* c8 ignore next */
  96. const argNodes =
  97. (_directiveNode$argume = directiveNode.arguments) !== null &&
  98. _directiveNode$argume !== void 0
  99. ? _directiveNode$argume
  100. : [];
  101. const argNodeMap = new Set(argNodes.map((arg) => arg.name.value));
  102. for (const [argName, argDef] of Object.entries(requiredArgs)) {
  103. if (!argNodeMap.has(argName)) {
  104. const argType = isType(argDef.type)
  105. ? inspect(argDef.type)
  106. : print(argDef.type);
  107. context.reportError(
  108. new GraphQLError(
  109. `Directive "@${directiveName}" argument "${argName}" of type "${argType}" is required, but it was not provided.`,
  110. {
  111. nodes: directiveNode,
  112. },
  113. ),
  114. );
  115. }
  116. }
  117. }
  118. },
  119. },
  120. };
  121. }
  122. function isRequiredArgumentNode(arg) {
  123. return arg.type.kind === Kind.NON_NULL_TYPE && arg.defaultValue == null;
  124. }