GraphQLError.js.flow 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. // @flow strict
  2. import isObjectLike from '../jsutils/isObjectLike';
  3. import { type ASTNode } from '../language/ast';
  4. import { type Source } from '../language/source';
  5. import { type SourceLocation, getLocation } from '../language/location';
  6. import { printLocation, printSourceLocation } from '../language/printLocation';
  7. /**
  8. * A GraphQLError describes an Error found during the parse, validate, or
  9. * execute phases of performing a GraphQL operation. In addition to a message
  10. * and stack trace, it also includes information about the locations in a
  11. * GraphQL document and/or execution result that correspond to the Error.
  12. */
  13. declare class GraphQLError extends Error {
  14. constructor(
  15. message: string,
  16. nodes?: $ReadOnlyArray<ASTNode> | ASTNode | void | null,
  17. source?: ?Source,
  18. positions?: ?$ReadOnlyArray<number>,
  19. path?: ?$ReadOnlyArray<string | number>,
  20. originalError?: ?Error,
  21. extensions?: ?{ [key: string]: mixed, ... },
  22. ): void;
  23. /**
  24. * A message describing the Error for debugging purposes.
  25. *
  26. * Enumerable, and appears in the result of JSON.stringify().
  27. *
  28. * Note: should be treated as readonly, despite invariant usage.
  29. */
  30. message: string;
  31. /**
  32. * An array of { line, column } locations within the source GraphQL document
  33. * which correspond to this error.
  34. *
  35. * Errors during validation often contain multiple locations, for example to
  36. * point out two things with the same name. Errors during execution include a
  37. * single location, the field which produced the error.
  38. *
  39. * Enumerable, and appears in the result of JSON.stringify().
  40. */
  41. +locations: $ReadOnlyArray<SourceLocation> | void;
  42. /**
  43. * An array describing the JSON-path into the execution response which
  44. * corresponds to this error. Only included for errors during execution.
  45. *
  46. * Enumerable, and appears in the result of JSON.stringify().
  47. */
  48. +path: $ReadOnlyArray<string | number> | void;
  49. /**
  50. * An array of GraphQL AST Nodes corresponding to this error.
  51. */
  52. +nodes: $ReadOnlyArray<ASTNode> | void;
  53. /**
  54. * The source GraphQL document for the first location of this error.
  55. *
  56. * Note that if this Error represents more than one node, the source may not
  57. * represent nodes after the first node.
  58. */
  59. +source: Source | void;
  60. /**
  61. * An array of character offsets within the source GraphQL document
  62. * which correspond to this error.
  63. */
  64. +positions: $ReadOnlyArray<number> | void;
  65. /**
  66. * The original error thrown from a field resolver during execution.
  67. */
  68. +originalError: ?Error;
  69. /**
  70. * Extension fields to add to the formatted error.
  71. */
  72. +extensions: { [key: string]: mixed, ... } | void;
  73. }
  74. export function GraphQLError( // eslint-disable-line no-redeclare
  75. message: string,
  76. nodes?: $ReadOnlyArray<ASTNode> | ASTNode | void,
  77. source?: ?Source,
  78. positions?: ?$ReadOnlyArray<number>,
  79. path?: ?$ReadOnlyArray<string | number>,
  80. originalError?: ?Error & { +extensions: mixed, ... },
  81. extensions?: ?{ [key: string]: mixed, ... },
  82. ) {
  83. // Compute list of blame nodes.
  84. const _nodes = Array.isArray(nodes)
  85. ? nodes.length !== 0
  86. ? nodes
  87. : undefined
  88. : nodes
  89. ? [nodes]
  90. : undefined;
  91. // Compute locations in the source for the given nodes/positions.
  92. let _source = source;
  93. if (!_source && _nodes) {
  94. const node = _nodes[0];
  95. _source = node && node.loc && node.loc.source;
  96. }
  97. let _positions = positions;
  98. if (!_positions && _nodes) {
  99. _positions = _nodes.reduce((list, node) => {
  100. if (node.loc) {
  101. list.push(node.loc.start);
  102. }
  103. return list;
  104. }, []);
  105. }
  106. if (_positions && _positions.length === 0) {
  107. _positions = undefined;
  108. }
  109. let _locations;
  110. if (positions && source) {
  111. _locations = positions.map(pos => getLocation(source, pos));
  112. } else if (_nodes) {
  113. _locations = _nodes.reduce((list, node) => {
  114. if (node.loc) {
  115. list.push(getLocation(node.loc.source, node.loc.start));
  116. }
  117. return list;
  118. }, []);
  119. }
  120. let _extensions = extensions;
  121. if (_extensions == null && originalError != null) {
  122. const originalExtensions = originalError.extensions;
  123. if (isObjectLike(originalExtensions)) {
  124. _extensions = originalExtensions;
  125. }
  126. }
  127. Object.defineProperties(this, {
  128. message: {
  129. value: message,
  130. // By being enumerable, JSON.stringify will include `message` in the
  131. // resulting output. This ensures that the simplest possible GraphQL
  132. // service adheres to the spec.
  133. enumerable: true,
  134. writable: true,
  135. },
  136. locations: {
  137. // Coercing falsey values to undefined ensures they will not be included
  138. // in JSON.stringify() when not provided.
  139. value: _locations || undefined,
  140. // By being enumerable, JSON.stringify will include `locations` in the
  141. // resulting output. This ensures that the simplest possible GraphQL
  142. // service adheres to the spec.
  143. enumerable: Boolean(_locations),
  144. },
  145. path: {
  146. // Coercing falsey values to undefined ensures they will not be included
  147. // in JSON.stringify() when not provided.
  148. value: path || undefined,
  149. // By being enumerable, JSON.stringify will include `path` in the
  150. // resulting output. This ensures that the simplest possible GraphQL
  151. // service adheres to the spec.
  152. enumerable: Boolean(path),
  153. },
  154. nodes: {
  155. value: _nodes || undefined,
  156. },
  157. source: {
  158. value: _source || undefined,
  159. },
  160. positions: {
  161. value: _positions || undefined,
  162. },
  163. originalError: {
  164. value: originalError,
  165. },
  166. extensions: {
  167. // Coercing falsey values to undefined ensures they will not be included
  168. // in JSON.stringify() when not provided.
  169. value: _extensions || undefined,
  170. // By being enumerable, JSON.stringify will include `path` in the
  171. // resulting output. This ensures that the simplest possible GraphQL
  172. // service adheres to the spec.
  173. enumerable: Boolean(_extensions),
  174. },
  175. });
  176. // Include (non-enumerable) stack trace.
  177. if (originalError && originalError.stack) {
  178. Object.defineProperty(this, 'stack', {
  179. value: originalError.stack,
  180. writable: true,
  181. configurable: true,
  182. });
  183. } else if (Error.captureStackTrace) {
  184. Error.captureStackTrace(this, GraphQLError);
  185. } else {
  186. Object.defineProperty(this, 'stack', {
  187. value: Error().stack,
  188. writable: true,
  189. configurable: true,
  190. });
  191. }
  192. }
  193. (GraphQLError: any).prototype = Object.create(Error.prototype, {
  194. constructor: { value: GraphQLError },
  195. name: { value: 'GraphQLError' },
  196. toString: {
  197. value: function toString() {
  198. return printError(this);
  199. },
  200. },
  201. });
  202. /**
  203. * Prints a GraphQLError to a string, representing useful location information
  204. * about the error's position in the source.
  205. */
  206. export function printError(error: GraphQLError): string {
  207. let output = error.message;
  208. if (error.nodes) {
  209. for (const node of error.nodes) {
  210. if (node.loc) {
  211. output += '\n\n' + printLocation(node.loc);
  212. }
  213. }
  214. } else if (error.source && error.locations) {
  215. for (const location of error.locations) {
  216. output += '\n\n' + printSourceLocation(error.source, location);
  217. }
  218. }
  219. return output;
  220. }