GraphQLError.js.flow 7.3 KB

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