requestError.tsx 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import {ResponseMeta} from 'sentry/api';
  2. import {sanitizePath} from './sanitizePath';
  3. export const ERROR_MAP = {
  4. 0: 'CancelledError',
  5. 200: 'UndefinedResponseBodyError',
  6. 400: 'BadRequestError',
  7. 401: 'UnauthorizedError',
  8. 403: 'ForbiddenError',
  9. 404: 'NotFoundError',
  10. 414: 'URITooLongError',
  11. 426: 'UpgradeRequiredError',
  12. 429: 'TooManyRequestsError',
  13. 500: 'InternalServerError',
  14. 501: 'NotImplementedError',
  15. 502: 'BadGatewayError',
  16. 503: 'ServiceUnavailableError',
  17. 504: 'GatewayTimeoutError',
  18. };
  19. // Technically, this should include the fact that `responseJSON` can be an
  20. // array, but since we never actually use its array-ness, it makes the typing
  21. // significantly simpler if we just rely on the "arrays are objects and we
  22. // can always check if a given property is defined on an object" principle
  23. type ResponseJSON = {
  24. [key: string]: unknown;
  25. detail?: string | {code?: string; message?: string};
  26. };
  27. export default class RequestError extends Error {
  28. responseText?: string;
  29. responseJSON?: ResponseJSON;
  30. status?: number;
  31. statusText?: string;
  32. constructor(
  33. method: 'POST' | 'GET' | 'DELETE' | 'PUT' | undefined,
  34. path: string,
  35. cause: Error,
  36. responseMetadata?: ResponseMeta
  37. ) {
  38. const options = cause instanceof Error ? {cause} : {};
  39. super(`${method || 'GET'} ${sanitizePath(path)}`, options);
  40. // TODO (kmclb) This is here to compensate for a bug in the SDK wherein it
  41. // ignores subclassing of `Error` when getting error type. Once that's
  42. // fixed, this can go. See https://github.com/getsentry/sentry-javascript/pull/8161.
  43. this.name = 'RequestError';
  44. this.addResponseMetadata(responseMetadata);
  45. }
  46. /**
  47. * Updates Error with XHR response
  48. */
  49. addResponseMetadata(resp: ResponseMeta | undefined) {
  50. if (resp) {
  51. // We filter 200's out unless they're the specific case of an undefined
  52. // body. For the ones which will eventually get filtered, we don't care
  53. // about the data added here and we don't want to change the name (or it
  54. // won't match the filter) so bail before any of that happens.
  55. //
  56. // TODO: If it turns out the undefined ones aren't really a problem, we
  57. // can remove this and the `200` entry in `ERROR_MAP` above
  58. if (resp.status === 200 && resp.responseText !== undefined) {
  59. return;
  60. }
  61. this.setNameFromStatus(resp.status);
  62. this.message = `${this.message} ${
  63. typeof resp.status === 'number' ? resp.status : 'n/a'
  64. }`;
  65. // Some callback handlers expect these properties on the error object
  66. if (resp.responseText) {
  67. this.responseText = resp.responseText;
  68. }
  69. if (resp.responseJSON) {
  70. this.responseJSON = resp.responseJSON;
  71. }
  72. this.status = resp.status;
  73. this.statusText = resp.statusText;
  74. }
  75. }
  76. setNameFromStatus(status: number) {
  77. const errorName = ERROR_MAP[status];
  78. if (errorName) {
  79. this.name = errorName;
  80. }
  81. }
  82. }