ProvidedRequiredArgumentsRule.js.flow 4.0 KB

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