ValidationContext.js.flow 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. // @flow strict
  2. import type { ObjMap } from '../jsutils/ObjMap';
  3. import type { GraphQLError } from '../error/GraphQLError';
  4. import type { ASTVisitor } from '../language/visitor';
  5. import type {
  6. DocumentNode,
  7. OperationDefinitionNode,
  8. VariableNode,
  9. SelectionSetNode,
  10. FragmentSpreadNode,
  11. FragmentDefinitionNode,
  12. } from '../language/ast';
  13. import { Kind } from '../language/kinds';
  14. import { visit } from '../language/visitor';
  15. import type { GraphQLSchema } from '../type/schema';
  16. import type { GraphQLDirective } from '../type/directives';
  17. import type {
  18. GraphQLInputType,
  19. GraphQLOutputType,
  20. GraphQLCompositeType,
  21. GraphQLField,
  22. GraphQLArgument,
  23. GraphQLEnumValue,
  24. } from '../type/definition';
  25. import { TypeInfo, visitWithTypeInfo } from '../utilities/TypeInfo';
  26. type NodeWithSelectionSet = OperationDefinitionNode | FragmentDefinitionNode;
  27. type VariableUsage = {|
  28. +node: VariableNode,
  29. +type: ?GraphQLInputType,
  30. +defaultValue: ?mixed,
  31. |};
  32. /**
  33. * An instance of this class is passed as the "this" context to all validators,
  34. * allowing access to commonly useful contextual information from within a
  35. * validation rule.
  36. */
  37. export class ASTValidationContext {
  38. _ast: DocumentNode;
  39. _onError: (err: GraphQLError) => void;
  40. _fragments: ?ObjMap<FragmentDefinitionNode>;
  41. _fragmentSpreads: Map<SelectionSetNode, $ReadOnlyArray<FragmentSpreadNode>>;
  42. _recursivelyReferencedFragments: Map<
  43. OperationDefinitionNode,
  44. $ReadOnlyArray<FragmentDefinitionNode>,
  45. >;
  46. constructor(ast: DocumentNode, onError: (err: GraphQLError) => void) {
  47. this._ast = ast;
  48. this._fragments = undefined;
  49. this._fragmentSpreads = new Map();
  50. this._recursivelyReferencedFragments = new Map();
  51. this._onError = onError;
  52. }
  53. reportError(error: GraphQLError): void {
  54. this._onError(error);
  55. }
  56. getDocument(): DocumentNode {
  57. return this._ast;
  58. }
  59. getFragment(name: string): ?FragmentDefinitionNode {
  60. let fragments = this._fragments;
  61. if (!fragments) {
  62. this._fragments = fragments = this.getDocument().definitions.reduce(
  63. (frags, statement) => {
  64. if (statement.kind === Kind.FRAGMENT_DEFINITION) {
  65. frags[statement.name.value] = statement;
  66. }
  67. return frags;
  68. },
  69. Object.create(null),
  70. );
  71. }
  72. return fragments[name];
  73. }
  74. getFragmentSpreads(
  75. node: SelectionSetNode,
  76. ): $ReadOnlyArray<FragmentSpreadNode> {
  77. let spreads = this._fragmentSpreads.get(node);
  78. if (!spreads) {
  79. spreads = [];
  80. const setsToVisit: Array<SelectionSetNode> = [node];
  81. while (setsToVisit.length !== 0) {
  82. const set = setsToVisit.pop();
  83. for (const selection of set.selections) {
  84. if (selection.kind === Kind.FRAGMENT_SPREAD) {
  85. spreads.push(selection);
  86. } else if (selection.selectionSet) {
  87. setsToVisit.push(selection.selectionSet);
  88. }
  89. }
  90. }
  91. this._fragmentSpreads.set(node, spreads);
  92. }
  93. return spreads;
  94. }
  95. getRecursivelyReferencedFragments(
  96. operation: OperationDefinitionNode,
  97. ): $ReadOnlyArray<FragmentDefinitionNode> {
  98. let fragments = this._recursivelyReferencedFragments.get(operation);
  99. if (!fragments) {
  100. fragments = [];
  101. const collectedNames = Object.create(null);
  102. const nodesToVisit: Array<SelectionSetNode> = [operation.selectionSet];
  103. while (nodesToVisit.length !== 0) {
  104. const node = nodesToVisit.pop();
  105. for (const spread of this.getFragmentSpreads(node)) {
  106. const fragName = spread.name.value;
  107. if (collectedNames[fragName] !== true) {
  108. collectedNames[fragName] = true;
  109. const fragment = this.getFragment(fragName);
  110. if (fragment) {
  111. fragments.push(fragment);
  112. nodesToVisit.push(fragment.selectionSet);
  113. }
  114. }
  115. }
  116. }
  117. this._recursivelyReferencedFragments.set(operation, fragments);
  118. }
  119. return fragments;
  120. }
  121. }
  122. export type ASTValidationRule = (ASTValidationContext) => ASTVisitor;
  123. export class SDLValidationContext extends ASTValidationContext {
  124. _schema: ?GraphQLSchema;
  125. constructor(
  126. ast: DocumentNode,
  127. schema: ?GraphQLSchema,
  128. onError: (err: GraphQLError) => void,
  129. ) {
  130. super(ast, onError);
  131. this._schema = schema;
  132. }
  133. getSchema(): ?GraphQLSchema {
  134. return this._schema;
  135. }
  136. }
  137. export type SDLValidationRule = (SDLValidationContext) => ASTVisitor;
  138. export class ValidationContext extends ASTValidationContext {
  139. _schema: GraphQLSchema;
  140. _typeInfo: TypeInfo;
  141. _variableUsages: Map<NodeWithSelectionSet, $ReadOnlyArray<VariableUsage>>;
  142. _recursiveVariableUsages: Map<
  143. OperationDefinitionNode,
  144. $ReadOnlyArray<VariableUsage>,
  145. >;
  146. constructor(
  147. schema: GraphQLSchema,
  148. ast: DocumentNode,
  149. typeInfo: TypeInfo,
  150. onError: (err: GraphQLError) => void,
  151. ) {
  152. super(ast, onError);
  153. this._schema = schema;
  154. this._typeInfo = typeInfo;
  155. this._variableUsages = new Map();
  156. this._recursiveVariableUsages = new Map();
  157. }
  158. getSchema(): GraphQLSchema {
  159. return this._schema;
  160. }
  161. getVariableUsages(node: NodeWithSelectionSet): $ReadOnlyArray<VariableUsage> {
  162. let usages = this._variableUsages.get(node);
  163. if (!usages) {
  164. const newUsages = [];
  165. const typeInfo = new TypeInfo(this._schema);
  166. visit(
  167. node,
  168. visitWithTypeInfo(typeInfo, {
  169. VariableDefinition: () => false,
  170. Variable(variable) {
  171. newUsages.push({
  172. node: variable,
  173. type: typeInfo.getInputType(),
  174. defaultValue: typeInfo.getDefaultValue(),
  175. });
  176. },
  177. }),
  178. );
  179. usages = newUsages;
  180. this._variableUsages.set(node, usages);
  181. }
  182. return usages;
  183. }
  184. getRecursiveVariableUsages(
  185. operation: OperationDefinitionNode,
  186. ): $ReadOnlyArray<VariableUsage> {
  187. let usages = this._recursiveVariableUsages.get(operation);
  188. if (!usages) {
  189. usages = this.getVariableUsages(operation);
  190. for (const frag of this.getRecursivelyReferencedFragments(operation)) {
  191. usages = usages.concat(this.getVariableUsages(frag));
  192. }
  193. this._recursiveVariableUsages.set(operation, usages);
  194. }
  195. return usages;
  196. }
  197. getType(): ?GraphQLOutputType {
  198. return this._typeInfo.getType();
  199. }
  200. getParentType(): ?GraphQLCompositeType {
  201. return this._typeInfo.getParentType();
  202. }
  203. getInputType(): ?GraphQLInputType {
  204. return this._typeInfo.getInputType();
  205. }
  206. getParentInputType(): ?GraphQLInputType {
  207. return this._typeInfo.getParentInputType();
  208. }
  209. getFieldDef(): ?GraphQLField<mixed, mixed> {
  210. return this._typeInfo.getFieldDef();
  211. }
  212. getDirective(): ?GraphQLDirective {
  213. return this._typeInfo.getDirective();
  214. }
  215. getArgument(): ?GraphQLArgument {
  216. return this._typeInfo.getArgument();
  217. }
  218. getEnumValue(): ?GraphQLEnumValue {
  219. return this._typeInfo.getEnumValue();
  220. }
  221. }
  222. export type ValidationRule = (ValidationContext) => ASTVisitor;