ValidationContext.mjs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import { Kind } from '../language/kinds.mjs';
  2. import { visit } from '../language/visitor.mjs';
  3. import { TypeInfo, visitWithTypeInfo } from '../utilities/TypeInfo.mjs';
  4. /**
  5. * An instance of this class is passed as the "this" context to all validators,
  6. * allowing access to commonly useful contextual information from within a
  7. * validation rule.
  8. */
  9. export class ASTValidationContext {
  10. constructor(ast, onError) {
  11. this._ast = ast;
  12. this._fragments = undefined;
  13. this._fragmentSpreads = new Map();
  14. this._recursivelyReferencedFragments = new Map();
  15. this._onError = onError;
  16. }
  17. get [Symbol.toStringTag]() {
  18. return 'ASTValidationContext';
  19. }
  20. reportError(error) {
  21. this._onError(error);
  22. }
  23. getDocument() {
  24. return this._ast;
  25. }
  26. getFragment(name) {
  27. let fragments;
  28. if (this._fragments) {
  29. fragments = this._fragments;
  30. } else {
  31. fragments = Object.create(null);
  32. for (const defNode of this.getDocument().definitions) {
  33. if (defNode.kind === Kind.FRAGMENT_DEFINITION) {
  34. fragments[defNode.name.value] = defNode;
  35. }
  36. }
  37. this._fragments = fragments;
  38. }
  39. return fragments[name];
  40. }
  41. getFragmentSpreads(node) {
  42. let spreads = this._fragmentSpreads.get(node);
  43. if (!spreads) {
  44. spreads = [];
  45. const setsToVisit = [node];
  46. let set;
  47. while ((set = setsToVisit.pop())) {
  48. for (const selection of set.selections) {
  49. if (selection.kind === Kind.FRAGMENT_SPREAD) {
  50. spreads.push(selection);
  51. } else if (selection.selectionSet) {
  52. setsToVisit.push(selection.selectionSet);
  53. }
  54. }
  55. }
  56. this._fragmentSpreads.set(node, spreads);
  57. }
  58. return spreads;
  59. }
  60. getRecursivelyReferencedFragments(operation) {
  61. let fragments = this._recursivelyReferencedFragments.get(operation);
  62. if (!fragments) {
  63. fragments = [];
  64. const collectedNames = Object.create(null);
  65. const nodesToVisit = [operation.selectionSet];
  66. let node;
  67. while ((node = nodesToVisit.pop())) {
  68. for (const spread of this.getFragmentSpreads(node)) {
  69. const fragName = spread.name.value;
  70. if (collectedNames[fragName] !== true) {
  71. collectedNames[fragName] = true;
  72. const fragment = this.getFragment(fragName);
  73. if (fragment) {
  74. fragments.push(fragment);
  75. nodesToVisit.push(fragment.selectionSet);
  76. }
  77. }
  78. }
  79. }
  80. this._recursivelyReferencedFragments.set(operation, fragments);
  81. }
  82. return fragments;
  83. }
  84. }
  85. export class SDLValidationContext extends ASTValidationContext {
  86. constructor(ast, schema, onError) {
  87. super(ast, onError);
  88. this._schema = schema;
  89. }
  90. get [Symbol.toStringTag]() {
  91. return 'SDLValidationContext';
  92. }
  93. getSchema() {
  94. return this._schema;
  95. }
  96. }
  97. export class ValidationContext extends ASTValidationContext {
  98. constructor(schema, ast, typeInfo, onError) {
  99. super(ast, onError);
  100. this._schema = schema;
  101. this._typeInfo = typeInfo;
  102. this._variableUsages = new Map();
  103. this._recursiveVariableUsages = new Map();
  104. }
  105. get [Symbol.toStringTag]() {
  106. return 'ValidationContext';
  107. }
  108. getSchema() {
  109. return this._schema;
  110. }
  111. getVariableUsages(node) {
  112. let usages = this._variableUsages.get(node);
  113. if (!usages) {
  114. const newUsages = [];
  115. const typeInfo = new TypeInfo(this._schema);
  116. visit(
  117. node,
  118. visitWithTypeInfo(typeInfo, {
  119. VariableDefinition: () => false,
  120. Variable(variable) {
  121. newUsages.push({
  122. node: variable,
  123. type: typeInfo.getInputType(),
  124. defaultValue: typeInfo.getDefaultValue(),
  125. });
  126. },
  127. }),
  128. );
  129. usages = newUsages;
  130. this._variableUsages.set(node, usages);
  131. }
  132. return usages;
  133. }
  134. getRecursiveVariableUsages(operation) {
  135. let usages = this._recursiveVariableUsages.get(operation);
  136. if (!usages) {
  137. usages = this.getVariableUsages(operation);
  138. for (const frag of this.getRecursivelyReferencedFragments(operation)) {
  139. usages = usages.concat(this.getVariableUsages(frag));
  140. }
  141. this._recursiveVariableUsages.set(operation, usages);
  142. }
  143. return usages;
  144. }
  145. getType() {
  146. return this._typeInfo.getType();
  147. }
  148. getParentType() {
  149. return this._typeInfo.getParentType();
  150. }
  151. getInputType() {
  152. return this._typeInfo.getInputType();
  153. }
  154. getParentInputType() {
  155. return this._typeInfo.getParentInputType();
  156. }
  157. getFieldDef() {
  158. return this._typeInfo.getFieldDef();
  159. }
  160. getDirective() {
  161. return this._typeInfo.getDirective();
  162. }
  163. getArgument() {
  164. return this._typeInfo.getArgument();
  165. }
  166. getEnumValue() {
  167. return this._typeInfo.getEnumValue();
  168. }
  169. }