KnownDirectivesRule.js.flow 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. // @flow strict
  2. import inspect from '../../jsutils/inspect';
  3. import invariant from '../../jsutils/invariant';
  4. import { GraphQLError } from '../../error/GraphQLError';
  5. import type { ASTVisitor } from '../../language/visitor';
  6. import type { ASTNode, OperationTypeNode } from '../../language/ast';
  7. import type { DirectiveLocationEnum } from '../../language/directiveLocation';
  8. import { Kind } from '../../language/kinds';
  9. import { DirectiveLocation } from '../../language/directiveLocation';
  10. import { specifiedDirectives } from '../../type/directives';
  11. import type {
  12. ValidationContext,
  13. SDLValidationContext,
  14. } from '../ValidationContext';
  15. /**
  16. * Known directives
  17. *
  18. * A GraphQL document is only valid if all `@directives` are known by the
  19. * schema and legally positioned.
  20. */
  21. export function KnownDirectivesRule(
  22. context: ValidationContext | SDLValidationContext,
  23. ): ASTVisitor {
  24. const locationsMap = Object.create(null);
  25. const schema = context.getSchema();
  26. const definedDirectives = schema
  27. ? schema.getDirectives()
  28. : specifiedDirectives;
  29. for (const directive of definedDirectives) {
  30. locationsMap[directive.name] = directive.locations;
  31. }
  32. const astDefinitions = context.getDocument().definitions;
  33. for (const def of astDefinitions) {
  34. if (def.kind === Kind.DIRECTIVE_DEFINITION) {
  35. locationsMap[def.name.value] = def.locations.map((name) => name.value);
  36. }
  37. }
  38. return {
  39. Directive(node, _key, _parent, _path, ancestors) {
  40. const name = node.name.value;
  41. const locations = locationsMap[name];
  42. if (!locations) {
  43. context.reportError(
  44. new GraphQLError(`Unknown directive "@${name}".`, node),
  45. );
  46. return;
  47. }
  48. const candidateLocation = getDirectiveLocationForASTPath(ancestors);
  49. if (candidateLocation && locations.indexOf(candidateLocation) === -1) {
  50. context.reportError(
  51. new GraphQLError(
  52. `Directive "@${name}" may not be used on ${candidateLocation}.`,
  53. node,
  54. ),
  55. );
  56. }
  57. },
  58. };
  59. }
  60. function getDirectiveLocationForASTPath(
  61. ancestors: $ReadOnlyArray<ASTNode | $ReadOnlyArray<ASTNode>>,
  62. ): DirectiveLocationEnum | void {
  63. const appliedTo = ancestors[ancestors.length - 1];
  64. invariant(!Array.isArray(appliedTo));
  65. switch (appliedTo.kind) {
  66. case Kind.OPERATION_DEFINITION:
  67. return getDirectiveLocationForOperation(appliedTo.operation);
  68. case Kind.FIELD:
  69. return DirectiveLocation.FIELD;
  70. case Kind.FRAGMENT_SPREAD:
  71. return DirectiveLocation.FRAGMENT_SPREAD;
  72. case Kind.INLINE_FRAGMENT:
  73. return DirectiveLocation.INLINE_FRAGMENT;
  74. case Kind.FRAGMENT_DEFINITION:
  75. return DirectiveLocation.FRAGMENT_DEFINITION;
  76. case Kind.VARIABLE_DEFINITION:
  77. return DirectiveLocation.VARIABLE_DEFINITION;
  78. case Kind.SCHEMA_DEFINITION:
  79. case Kind.SCHEMA_EXTENSION:
  80. return DirectiveLocation.SCHEMA;
  81. case Kind.SCALAR_TYPE_DEFINITION:
  82. case Kind.SCALAR_TYPE_EXTENSION:
  83. return DirectiveLocation.SCALAR;
  84. case Kind.OBJECT_TYPE_DEFINITION:
  85. case Kind.OBJECT_TYPE_EXTENSION:
  86. return DirectiveLocation.OBJECT;
  87. case Kind.FIELD_DEFINITION:
  88. return DirectiveLocation.FIELD_DEFINITION;
  89. case Kind.INTERFACE_TYPE_DEFINITION:
  90. case Kind.INTERFACE_TYPE_EXTENSION:
  91. return DirectiveLocation.INTERFACE;
  92. case Kind.UNION_TYPE_DEFINITION:
  93. case Kind.UNION_TYPE_EXTENSION:
  94. return DirectiveLocation.UNION;
  95. case Kind.ENUM_TYPE_DEFINITION:
  96. case Kind.ENUM_TYPE_EXTENSION:
  97. return DirectiveLocation.ENUM;
  98. case Kind.ENUM_VALUE_DEFINITION:
  99. return DirectiveLocation.ENUM_VALUE;
  100. case Kind.INPUT_OBJECT_TYPE_DEFINITION:
  101. case Kind.INPUT_OBJECT_TYPE_EXTENSION:
  102. return DirectiveLocation.INPUT_OBJECT;
  103. case Kind.INPUT_VALUE_DEFINITION: {
  104. const parentNode = ancestors[ancestors.length - 3];
  105. return parentNode.kind === Kind.INPUT_OBJECT_TYPE_DEFINITION
  106. ? DirectiveLocation.INPUT_FIELD_DEFINITION
  107. : DirectiveLocation.ARGUMENT_DEFINITION;
  108. }
  109. }
  110. }
  111. function getDirectiveLocationForOperation(
  112. operation: OperationTypeNode,
  113. ): DirectiveLocationEnum {
  114. switch (operation) {
  115. case 'query':
  116. return DirectiveLocation.QUERY;
  117. case 'mutation':
  118. return DirectiveLocation.MUTATION;
  119. case 'subscription':
  120. return DirectiveLocation.SUBSCRIPTION;
  121. }
  122. // istanbul ignore next (Not reachable. All possible types have been considered)
  123. invariant(false, 'Unexpected operation: ' + inspect((operation: empty)));
  124. }