ProvidedRequiredArgumentsRule.js.flow 3.9 KB

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