error.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. import type { Document } from './bson';
  2. import type { TopologyVersion } from './sdam/server_description';
  3. import type { TopologyDescription } from './sdam/topology_description';
  4. /** @public */
  5. export type AnyError = MongoError | Error;
  6. /** @internal */
  7. const kErrorLabels = Symbol('errorLabels');
  8. /**
  9. * @internal
  10. * The legacy error message from the server that indicates the node is not a writable primary
  11. * https://github.com/mongodb/specifications/blob/b07c26dc40d04ac20349f989db531c9845fdd755/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#not-writable-primary-and-node-is-recovering
  12. */
  13. export const LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE = new RegExp('not master', 'i');
  14. /**
  15. * @internal
  16. * The legacy error message from the server that indicates the node is not a primary or secondary
  17. * https://github.com/mongodb/specifications/blob/b07c26dc40d04ac20349f989db531c9845fdd755/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#not-writable-primary-and-node-is-recovering
  18. */
  19. export const LEGACY_NOT_PRIMARY_OR_SECONDARY_ERROR_MESSAGE = new RegExp(
  20. 'not master or secondary',
  21. 'i'
  22. );
  23. /**
  24. * @internal
  25. * The error message from the server that indicates the node is recovering
  26. * https://github.com/mongodb/specifications/blob/b07c26dc40d04ac20349f989db531c9845fdd755/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#not-writable-primary-and-node-is-recovering
  27. */
  28. export const NODE_IS_RECOVERING_ERROR_MESSAGE = new RegExp('node is recovering', 'i');
  29. /** @internal MongoDB Error Codes */
  30. export const MONGODB_ERROR_CODES = Object.freeze({
  31. HostUnreachable: 6,
  32. HostNotFound: 7,
  33. NetworkTimeout: 89,
  34. ShutdownInProgress: 91,
  35. PrimarySteppedDown: 189,
  36. ExceededTimeLimit: 262,
  37. SocketException: 9001,
  38. NotWritablePrimary: 10107,
  39. InterruptedAtShutdown: 11600,
  40. InterruptedDueToReplStateChange: 11602,
  41. NotPrimaryNoSecondaryOk: 13435,
  42. NotPrimaryOrSecondary: 13436,
  43. StaleShardVersion: 63,
  44. StaleEpoch: 150,
  45. StaleConfig: 13388,
  46. RetryChangeStream: 234,
  47. FailedToSatisfyReadPreference: 133,
  48. CursorNotFound: 43,
  49. LegacyNotPrimary: 10058,
  50. WriteConcernFailed: 64,
  51. NamespaceNotFound: 26,
  52. IllegalOperation: 20,
  53. MaxTimeMSExpired: 50,
  54. UnknownReplWriteConcern: 79,
  55. UnsatisfiableWriteConcern: 100,
  56. Reauthenticate: 391
  57. } as const);
  58. // From spec@https://github.com/mongodb/specifications/blob/f93d78191f3db2898a59013a7ed5650352ef6da8/source/change-streams/change-streams.rst#resumable-error
  59. export const GET_MORE_RESUMABLE_CODES = new Set<number>([
  60. MONGODB_ERROR_CODES.HostUnreachable,
  61. MONGODB_ERROR_CODES.HostNotFound,
  62. MONGODB_ERROR_CODES.NetworkTimeout,
  63. MONGODB_ERROR_CODES.ShutdownInProgress,
  64. MONGODB_ERROR_CODES.PrimarySteppedDown,
  65. MONGODB_ERROR_CODES.ExceededTimeLimit,
  66. MONGODB_ERROR_CODES.SocketException,
  67. MONGODB_ERROR_CODES.NotWritablePrimary,
  68. MONGODB_ERROR_CODES.InterruptedAtShutdown,
  69. MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
  70. MONGODB_ERROR_CODES.NotPrimaryNoSecondaryOk,
  71. MONGODB_ERROR_CODES.NotPrimaryOrSecondary,
  72. MONGODB_ERROR_CODES.StaleShardVersion,
  73. MONGODB_ERROR_CODES.StaleEpoch,
  74. MONGODB_ERROR_CODES.StaleConfig,
  75. MONGODB_ERROR_CODES.RetryChangeStream,
  76. MONGODB_ERROR_CODES.FailedToSatisfyReadPreference,
  77. MONGODB_ERROR_CODES.CursorNotFound
  78. ]);
  79. /** @public */
  80. export const MongoErrorLabel = Object.freeze({
  81. RetryableWriteError: 'RetryableWriteError',
  82. TransientTransactionError: 'TransientTransactionError',
  83. UnknownTransactionCommitResult: 'UnknownTransactionCommitResult',
  84. ResumableChangeStreamError: 'ResumableChangeStreamError',
  85. HandshakeError: 'HandshakeError',
  86. ResetPool: 'ResetPool',
  87. InterruptInUseConnections: 'InterruptInUseConnections',
  88. NoWritesPerformed: 'NoWritesPerformed'
  89. } as const);
  90. /** @public */
  91. export type MongoErrorLabel = (typeof MongoErrorLabel)[keyof typeof MongoErrorLabel];
  92. /** @public */
  93. export interface ErrorDescription extends Document {
  94. message?: string;
  95. errmsg?: string;
  96. $err?: string;
  97. errorLabels?: string[];
  98. errInfo?: Document;
  99. }
  100. function isAggregateError(e: Error): e is Error & { errors: Error[] } {
  101. return 'errors' in e && Array.isArray(e.errors);
  102. }
  103. /**
  104. * @public
  105. * @category Error
  106. *
  107. * @privateRemarks
  108. * mongodb-client-encryption has a dependency on this error, it uses the constructor with a string argument
  109. */
  110. export class MongoError extends Error {
  111. /** @internal */
  112. [kErrorLabels]: Set<string>;
  113. /**
  114. * This is a number in MongoServerError and a string in MongoDriverError
  115. * @privateRemarks
  116. * Define the type override on the subclasses when we can use the override keyword
  117. */
  118. code?: number | string;
  119. topologyVersion?: TopologyVersion;
  120. connectionGeneration?: number;
  121. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  122. // @ts-ignore
  123. cause?: Error; // depending on the node version, this may or may not exist on the base
  124. constructor(message: string | Error) {
  125. super(MongoError.buildErrorMessage(message));
  126. if (message instanceof Error) {
  127. this.cause = message;
  128. }
  129. this[kErrorLabels] = new Set();
  130. }
  131. /** @internal */
  132. private static buildErrorMessage(e: Error | string): string {
  133. if (typeof e === 'string') {
  134. return e;
  135. }
  136. if (isAggregateError(e) && e.message.length === 0) {
  137. return e.errors.length === 0
  138. ? 'AggregateError has an empty errors array. Please check the `cause` property for more information.'
  139. : e.errors.map(({ message }) => message).join(', ');
  140. }
  141. return e.message;
  142. }
  143. override get name(): string {
  144. return 'MongoError';
  145. }
  146. /** Legacy name for server error responses */
  147. get errmsg(): string {
  148. return this.message;
  149. }
  150. /**
  151. * Checks the error to see if it has an error label
  152. *
  153. * @param label - The error label to check for
  154. * @returns returns true if the error has the provided error label
  155. */
  156. hasErrorLabel(label: string): boolean {
  157. return this[kErrorLabels].has(label);
  158. }
  159. addErrorLabel(label: string): void {
  160. this[kErrorLabels].add(label);
  161. }
  162. get errorLabels(): string[] {
  163. return Array.from(this[kErrorLabels]);
  164. }
  165. }
  166. /**
  167. * An error coming from the mongo server
  168. *
  169. * @public
  170. * @category Error
  171. */
  172. export class MongoServerError extends MongoError {
  173. codeName?: string;
  174. writeConcernError?: Document;
  175. errInfo?: Document;
  176. ok?: number;
  177. [key: string]: any;
  178. constructor(message: ErrorDescription) {
  179. super(message.message || message.errmsg || message.$err || 'n/a');
  180. if (message.errorLabels) {
  181. this[kErrorLabels] = new Set(message.errorLabels);
  182. }
  183. for (const name in message) {
  184. if (name !== 'errorLabels' && name !== 'errmsg' && name !== 'message')
  185. this[name] = message[name];
  186. }
  187. }
  188. override get name(): string {
  189. return 'MongoServerError';
  190. }
  191. }
  192. /**
  193. * An error generated by the driver
  194. *
  195. * @public
  196. * @category Error
  197. */
  198. export class MongoDriverError extends MongoError {
  199. constructor(message: string) {
  200. super(message);
  201. }
  202. override get name(): string {
  203. return 'MongoDriverError';
  204. }
  205. }
  206. /**
  207. * An error generated when the driver API is used incorrectly
  208. *
  209. * @privateRemarks
  210. * Should **never** be directly instantiated
  211. *
  212. * @public
  213. * @category Error
  214. */
  215. export class MongoAPIError extends MongoDriverError {
  216. constructor(message: string) {
  217. super(message);
  218. }
  219. override get name(): string {
  220. return 'MongoAPIError';
  221. }
  222. }
  223. /**
  224. * An error generated when the driver encounters unexpected input
  225. * or reaches an unexpected/invalid internal state
  226. *
  227. * @privateRemarks
  228. * Should **never** be directly instantiated.
  229. *
  230. * @public
  231. * @category Error
  232. */
  233. export class MongoRuntimeError extends MongoDriverError {
  234. constructor(message: string) {
  235. super(message);
  236. }
  237. override get name(): string {
  238. return 'MongoRuntimeError';
  239. }
  240. }
  241. /**
  242. * An error generated when a batch command is re-executed after one of the commands in the batch
  243. * has failed
  244. *
  245. * @public
  246. * @category Error
  247. */
  248. export class MongoBatchReExecutionError extends MongoAPIError {
  249. constructor(message = 'This batch has already been executed, create new batch to execute') {
  250. super(message);
  251. }
  252. override get name(): string {
  253. return 'MongoBatchReExecutionError';
  254. }
  255. }
  256. /**
  257. * An error generated when the driver fails to decompress
  258. * data received from the server.
  259. *
  260. * @public
  261. * @category Error
  262. */
  263. export class MongoDecompressionError extends MongoRuntimeError {
  264. constructor(message: string) {
  265. super(message);
  266. }
  267. override get name(): string {
  268. return 'MongoDecompressionError';
  269. }
  270. }
  271. /**
  272. * An error thrown when the user attempts to operate on a database or collection through a MongoClient
  273. * that has not yet successfully called the "connect" method
  274. *
  275. * @public
  276. * @category Error
  277. */
  278. export class MongoNotConnectedError extends MongoAPIError {
  279. constructor(message: string) {
  280. super(message);
  281. }
  282. override get name(): string {
  283. return 'MongoNotConnectedError';
  284. }
  285. }
  286. /**
  287. * An error generated when the user makes a mistake in the usage of transactions.
  288. * (e.g. attempting to commit a transaction with a readPreference other than primary)
  289. *
  290. * @public
  291. * @category Error
  292. */
  293. export class MongoTransactionError extends MongoAPIError {
  294. constructor(message: string) {
  295. super(message);
  296. }
  297. override get name(): string {
  298. return 'MongoTransactionError';
  299. }
  300. }
  301. /**
  302. * An error generated when the user attempts to operate
  303. * on a session that has expired or has been closed.
  304. *
  305. * @public
  306. * @category Error
  307. */
  308. export class MongoExpiredSessionError extends MongoAPIError {
  309. constructor(message = 'Cannot use a session that has ended') {
  310. super(message);
  311. }
  312. override get name(): string {
  313. return 'MongoExpiredSessionError';
  314. }
  315. }
  316. /**
  317. * A error generated when the user attempts to authenticate
  318. * via Kerberos, but fails to connect to the Kerberos client.
  319. *
  320. * @public
  321. * @category Error
  322. */
  323. export class MongoKerberosError extends MongoRuntimeError {
  324. constructor(message: string) {
  325. super(message);
  326. }
  327. override get name(): string {
  328. return 'MongoKerberosError';
  329. }
  330. }
  331. /**
  332. * A error generated when the user attempts to authenticate
  333. * via AWS, but fails
  334. *
  335. * @public
  336. * @category Error
  337. */
  338. export class MongoAWSError extends MongoRuntimeError {
  339. constructor(message: string) {
  340. super(message);
  341. }
  342. override get name(): string {
  343. return 'MongoAWSError';
  344. }
  345. }
  346. /**
  347. * A error generated when the user attempts to authenticate
  348. * via Azure, but fails.
  349. *
  350. * @public
  351. * @category Error
  352. */
  353. export class MongoAzureError extends MongoRuntimeError {
  354. constructor(message: string) {
  355. super(message);
  356. }
  357. override get name(): string {
  358. return 'MongoAzureError';
  359. }
  360. }
  361. /**
  362. * An error generated when a ChangeStream operation fails to execute.
  363. *
  364. * @public
  365. * @category Error
  366. */
  367. export class MongoChangeStreamError extends MongoRuntimeError {
  368. constructor(message: string) {
  369. super(message);
  370. }
  371. override get name(): string {
  372. return 'MongoChangeStreamError';
  373. }
  374. }
  375. /**
  376. * An error thrown when the user calls a function or method not supported on a tailable cursor
  377. *
  378. * @public
  379. * @category Error
  380. */
  381. export class MongoTailableCursorError extends MongoAPIError {
  382. constructor(message = 'Tailable cursor does not support this operation') {
  383. super(message);
  384. }
  385. override get name(): string {
  386. return 'MongoTailableCursorError';
  387. }
  388. }
  389. /** An error generated when a GridFSStream operation fails to execute.
  390. *
  391. * @public
  392. * @category Error
  393. */
  394. export class MongoGridFSStreamError extends MongoRuntimeError {
  395. constructor(message: string) {
  396. super(message);
  397. }
  398. override get name(): string {
  399. return 'MongoGridFSStreamError';
  400. }
  401. }
  402. /**
  403. * An error generated when a malformed or invalid chunk is
  404. * encountered when reading from a GridFSStream.
  405. *
  406. * @public
  407. * @category Error
  408. */
  409. export class MongoGridFSChunkError extends MongoRuntimeError {
  410. constructor(message: string) {
  411. super(message);
  412. }
  413. override get name(): string {
  414. return 'MongoGridFSChunkError';
  415. }
  416. }
  417. /**
  418. * An error generated when a **parsable** unexpected response comes from the server.
  419. * This is generally an error where the driver in a state expecting a certain behavior to occur in
  420. * the next message from MongoDB but it receives something else.
  421. * This error **does not** represent an issue with wire message formatting.
  422. *
  423. * #### Example
  424. * When an operation fails, it is the driver's job to retry it. It must perform serverSelection
  425. * again to make sure that it attempts the operation against a server in a good state. If server
  426. * selection returns a server that does not support retryable operations, this error is used.
  427. * This scenario is unlikely as retryable support would also have been determined on the first attempt
  428. * but it is possible the state change could report a selectable server that does not support retries.
  429. *
  430. * @public
  431. * @category Error
  432. */
  433. export class MongoUnexpectedServerResponseError extends MongoRuntimeError {
  434. constructor(message: string) {
  435. super(message);
  436. }
  437. override get name(): string {
  438. return 'MongoUnexpectedServerResponseError';
  439. }
  440. }
  441. /**
  442. * An error thrown when the user attempts to add options to a cursor that has already been
  443. * initialized
  444. *
  445. * @public
  446. * @category Error
  447. */
  448. export class MongoCursorInUseError extends MongoAPIError {
  449. constructor(message = 'Cursor is already initialized') {
  450. super(message);
  451. }
  452. override get name(): string {
  453. return 'MongoCursorInUseError';
  454. }
  455. }
  456. /**
  457. * An error generated when an attempt is made to operate
  458. * on a closed/closing server.
  459. *
  460. * @public
  461. * @category Error
  462. */
  463. export class MongoServerClosedError extends MongoAPIError {
  464. constructor(message = 'Server is closed') {
  465. super(message);
  466. }
  467. override get name(): string {
  468. return 'MongoServerClosedError';
  469. }
  470. }
  471. /**
  472. * An error thrown when an attempt is made to read from a cursor that has been exhausted
  473. *
  474. * @public
  475. * @category Error
  476. */
  477. export class MongoCursorExhaustedError extends MongoAPIError {
  478. constructor(message?: string) {
  479. super(message || 'Cursor is exhausted');
  480. }
  481. override get name(): string {
  482. return 'MongoCursorExhaustedError';
  483. }
  484. }
  485. /**
  486. * An error generated when an attempt is made to operate on a
  487. * dropped, or otherwise unavailable, database.
  488. *
  489. * @public
  490. * @category Error
  491. */
  492. export class MongoTopologyClosedError extends MongoAPIError {
  493. constructor(message = 'Topology is closed') {
  494. super(message);
  495. }
  496. override get name(): string {
  497. return 'MongoTopologyClosedError';
  498. }
  499. }
  500. /** @internal */
  501. const kBeforeHandshake = Symbol('beforeHandshake');
  502. export function isNetworkErrorBeforeHandshake(err: MongoNetworkError): boolean {
  503. return err[kBeforeHandshake] === true;
  504. }
  505. /** @public */
  506. export interface MongoNetworkErrorOptions {
  507. /** Indicates the timeout happened before a connection handshake completed */
  508. beforeHandshake: boolean;
  509. }
  510. /**
  511. * An error indicating an issue with the network, including TCP errors and timeouts.
  512. * @public
  513. * @category Error
  514. */
  515. export class MongoNetworkError extends MongoError {
  516. /** @internal */
  517. [kBeforeHandshake]?: boolean;
  518. constructor(message: string | Error, options?: MongoNetworkErrorOptions) {
  519. super(message);
  520. if (options && typeof options.beforeHandshake === 'boolean') {
  521. this[kBeforeHandshake] = options.beforeHandshake;
  522. }
  523. }
  524. override get name(): string {
  525. return 'MongoNetworkError';
  526. }
  527. }
  528. /**
  529. * An error indicating a network timeout occurred
  530. * @public
  531. * @category Error
  532. *
  533. * @privateRemarks
  534. * mongodb-client-encryption has a dependency on this error with an instanceof check
  535. */
  536. export class MongoNetworkTimeoutError extends MongoNetworkError {
  537. constructor(message: string, options?: MongoNetworkErrorOptions) {
  538. super(message, options);
  539. }
  540. override get name(): string {
  541. return 'MongoNetworkTimeoutError';
  542. }
  543. }
  544. /**
  545. * An error used when attempting to parse a value (like a connection string)
  546. * @public
  547. * @category Error
  548. */
  549. export class MongoParseError extends MongoDriverError {
  550. constructor(message: string) {
  551. super(message);
  552. }
  553. override get name(): string {
  554. return 'MongoParseError';
  555. }
  556. }
  557. /**
  558. * An error generated when the user supplies malformed or unexpected arguments
  559. * or when a required argument or field is not provided.
  560. *
  561. *
  562. * @public
  563. * @category Error
  564. */
  565. export class MongoInvalidArgumentError extends MongoAPIError {
  566. constructor(message: string) {
  567. super(message);
  568. }
  569. override get name(): string {
  570. return 'MongoInvalidArgumentError';
  571. }
  572. }
  573. /**
  574. * An error generated when a feature that is not enabled or allowed for the current server
  575. * configuration is used
  576. *
  577. *
  578. * @public
  579. * @category Error
  580. */
  581. export class MongoCompatibilityError extends MongoAPIError {
  582. constructor(message: string) {
  583. super(message);
  584. }
  585. override get name(): string {
  586. return 'MongoCompatibilityError';
  587. }
  588. }
  589. /**
  590. * An error generated when the user fails to provide authentication credentials before attempting
  591. * to connect to a mongo server instance.
  592. *
  593. *
  594. * @public
  595. * @category Error
  596. */
  597. export class MongoMissingCredentialsError extends MongoAPIError {
  598. constructor(message: string) {
  599. super(message);
  600. }
  601. override get name(): string {
  602. return 'MongoMissingCredentialsError';
  603. }
  604. }
  605. /**
  606. * An error generated when a required module or dependency is not present in the local environment
  607. *
  608. * @public
  609. * @category Error
  610. */
  611. export class MongoMissingDependencyError extends MongoAPIError {
  612. constructor(message: string, { cause }: { cause?: Error } = {}) {
  613. super(message);
  614. if (cause) this.cause = cause;
  615. }
  616. override get name(): string {
  617. return 'MongoMissingDependencyError';
  618. }
  619. }
  620. /**
  621. * An error signifying a general system issue
  622. * @public
  623. * @category Error
  624. */
  625. export class MongoSystemError extends MongoError {
  626. /** An optional reason context, such as an error saved during flow of monitoring and selecting servers */
  627. reason?: TopologyDescription;
  628. constructor(message: string, reason: TopologyDescription) {
  629. if (reason && reason.error) {
  630. super(reason.error.message || reason.error);
  631. } else {
  632. super(message);
  633. }
  634. if (reason) {
  635. this.reason = reason;
  636. }
  637. this.code = reason.error?.code;
  638. }
  639. override get name(): string {
  640. return 'MongoSystemError';
  641. }
  642. }
  643. /**
  644. * An error signifying a client-side server selection error
  645. * @public
  646. * @category Error
  647. */
  648. export class MongoServerSelectionError extends MongoSystemError {
  649. constructor(message: string, reason: TopologyDescription) {
  650. super(message, reason);
  651. }
  652. override get name(): string {
  653. return 'MongoServerSelectionError';
  654. }
  655. }
  656. function makeWriteConcernResultObject(input: any) {
  657. const output = Object.assign({}, input);
  658. if (output.ok === 0) {
  659. output.ok = 1;
  660. delete output.errmsg;
  661. delete output.code;
  662. delete output.codeName;
  663. }
  664. return output;
  665. }
  666. /**
  667. * An error thrown when the server reports a writeConcernError
  668. * @public
  669. * @category Error
  670. */
  671. export class MongoWriteConcernError extends MongoServerError {
  672. /** The result document (provided if ok: 1) */
  673. result?: Document;
  674. constructor(message: ErrorDescription, result?: Document) {
  675. if (result && Array.isArray(result.errorLabels)) {
  676. message.errorLabels = result.errorLabels;
  677. }
  678. super(message);
  679. this.errInfo = message.errInfo;
  680. if (result != null) {
  681. this.result = makeWriteConcernResultObject(result);
  682. }
  683. }
  684. override get name(): string {
  685. return 'MongoWriteConcernError';
  686. }
  687. }
  688. // https://github.com/mongodb/specifications/blob/master/source/retryable-reads/retryable-reads.rst#retryable-error
  689. const RETRYABLE_READ_ERROR_CODES = new Set<number>([
  690. MONGODB_ERROR_CODES.HostUnreachable,
  691. MONGODB_ERROR_CODES.HostNotFound,
  692. MONGODB_ERROR_CODES.NetworkTimeout,
  693. MONGODB_ERROR_CODES.ShutdownInProgress,
  694. MONGODB_ERROR_CODES.PrimarySteppedDown,
  695. MONGODB_ERROR_CODES.SocketException,
  696. MONGODB_ERROR_CODES.NotWritablePrimary,
  697. MONGODB_ERROR_CODES.InterruptedAtShutdown,
  698. MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
  699. MONGODB_ERROR_CODES.NotPrimaryNoSecondaryOk,
  700. MONGODB_ERROR_CODES.NotPrimaryOrSecondary
  701. ]);
  702. // see: https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst#terms
  703. const RETRYABLE_WRITE_ERROR_CODES = new Set<number>([
  704. ...RETRYABLE_READ_ERROR_CODES,
  705. MONGODB_ERROR_CODES.ExceededTimeLimit
  706. ]);
  707. export function needsRetryableWriteLabel(error: Error, maxWireVersion: number): boolean {
  708. // pre-4.4 server, then the driver adds an error label for every valid case
  709. // execute operation will only inspect the label, code/message logic is handled here
  710. if (error instanceof MongoNetworkError) {
  711. return true;
  712. }
  713. if (error instanceof MongoError) {
  714. if (
  715. (maxWireVersion >= 9 || error.hasErrorLabel(MongoErrorLabel.RetryableWriteError)) &&
  716. !error.hasErrorLabel(MongoErrorLabel.HandshakeError)
  717. ) {
  718. // If we already have the error label no need to add it again. 4.4+ servers add the label.
  719. // In the case where we have a handshake error, need to fall down to the logic checking
  720. // the codes.
  721. return false;
  722. }
  723. }
  724. if (error instanceof MongoWriteConcernError) {
  725. return RETRYABLE_WRITE_ERROR_CODES.has(error.result?.code ?? error.code ?? 0);
  726. }
  727. if (error instanceof MongoError && typeof error.code === 'number') {
  728. return RETRYABLE_WRITE_ERROR_CODES.has(error.code);
  729. }
  730. const isNotWritablePrimaryError = LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE.test(error.message);
  731. if (isNotWritablePrimaryError) {
  732. return true;
  733. }
  734. const isNodeIsRecoveringError = NODE_IS_RECOVERING_ERROR_MESSAGE.test(error.message);
  735. if (isNodeIsRecoveringError) {
  736. return true;
  737. }
  738. return false;
  739. }
  740. export function isRetryableWriteError(error: MongoError): boolean {
  741. return error.hasErrorLabel(MongoErrorLabel.RetryableWriteError);
  742. }
  743. /** Determines whether an error is something the driver should attempt to retry */
  744. export function isRetryableReadError(error: MongoError): boolean {
  745. const hasRetryableErrorCode =
  746. typeof error.code === 'number' ? RETRYABLE_READ_ERROR_CODES.has(error.code) : false;
  747. if (hasRetryableErrorCode) {
  748. return true;
  749. }
  750. if (error instanceof MongoNetworkError) {
  751. return true;
  752. }
  753. const isNotWritablePrimaryError = LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE.test(error.message);
  754. if (isNotWritablePrimaryError) {
  755. return true;
  756. }
  757. const isNodeIsRecoveringError = NODE_IS_RECOVERING_ERROR_MESSAGE.test(error.message);
  758. if (isNodeIsRecoveringError) {
  759. return true;
  760. }
  761. return false;
  762. }
  763. const SDAM_RECOVERING_CODES = new Set<number>([
  764. MONGODB_ERROR_CODES.ShutdownInProgress,
  765. MONGODB_ERROR_CODES.PrimarySteppedDown,
  766. MONGODB_ERROR_CODES.InterruptedAtShutdown,
  767. MONGODB_ERROR_CODES.InterruptedDueToReplStateChange,
  768. MONGODB_ERROR_CODES.NotPrimaryOrSecondary
  769. ]);
  770. const SDAM_NOT_PRIMARY_CODES = new Set<number>([
  771. MONGODB_ERROR_CODES.NotWritablePrimary,
  772. MONGODB_ERROR_CODES.NotPrimaryNoSecondaryOk,
  773. MONGODB_ERROR_CODES.LegacyNotPrimary
  774. ]);
  775. const SDAM_NODE_SHUTTING_DOWN_ERROR_CODES = new Set<number>([
  776. MONGODB_ERROR_CODES.InterruptedAtShutdown,
  777. MONGODB_ERROR_CODES.ShutdownInProgress
  778. ]);
  779. function isRecoveringError(err: MongoError) {
  780. if (typeof err.code === 'number') {
  781. // If any error code exists, we ignore the error.message
  782. return SDAM_RECOVERING_CODES.has(err.code);
  783. }
  784. return (
  785. LEGACY_NOT_PRIMARY_OR_SECONDARY_ERROR_MESSAGE.test(err.message) ||
  786. NODE_IS_RECOVERING_ERROR_MESSAGE.test(err.message)
  787. );
  788. }
  789. function isNotWritablePrimaryError(err: MongoError) {
  790. if (typeof err.code === 'number') {
  791. // If any error code exists, we ignore the error.message
  792. return SDAM_NOT_PRIMARY_CODES.has(err.code);
  793. }
  794. if (isRecoveringError(err)) {
  795. return false;
  796. }
  797. return LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE.test(err.message);
  798. }
  799. export function isNodeShuttingDownError(err: MongoError): boolean {
  800. return !!(typeof err.code === 'number' && SDAM_NODE_SHUTTING_DOWN_ERROR_CODES.has(err.code));
  801. }
  802. /**
  803. * Determines whether SDAM can recover from a given error. If it cannot
  804. * then the pool will be cleared, and server state will completely reset
  805. * locally.
  806. *
  807. * @see https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#not-master-and-node-is-recovering
  808. */
  809. export function isSDAMUnrecoverableError(error: MongoError): boolean {
  810. // NOTE: null check is here for a strictly pre-CMAP world, a timeout or
  811. // close event are considered unrecoverable
  812. if (error instanceof MongoParseError || error == null) {
  813. return true;
  814. }
  815. return isRecoveringError(error) || isNotWritablePrimaryError(error);
  816. }
  817. export function isNetworkTimeoutError(err: MongoError): err is MongoNetworkError {
  818. return !!(err instanceof MongoNetworkError && err.message.match(/timed out/));
  819. }
  820. export function isResumableError(error?: Error, wireVersion?: number): boolean {
  821. if (error == null || !(error instanceof MongoError)) {
  822. return false;
  823. }
  824. if (error instanceof MongoNetworkError) {
  825. return true;
  826. }
  827. if (wireVersion != null && wireVersion >= 9) {
  828. // DRIVERS-1308: For 4.4 drivers running against 4.4 servers, drivers will add a special case to treat the CursorNotFound error code as resumable
  829. if (error.code === MONGODB_ERROR_CODES.CursorNotFound) {
  830. return true;
  831. }
  832. return error.hasErrorLabel(MongoErrorLabel.ResumableChangeStreamError);
  833. }
  834. if (typeof error.code === 'number') {
  835. return GET_MORE_RESUMABLE_CODES.has(error.code);
  836. }
  837. return false;
  838. }