Skip to content

Commit ba20eca

Browse files
fix(NODE-5311): construct error messages for AggregateErrors in Node16+ (#3683)
1 parent 0becdac commit ba20eca

File tree

4 files changed

+100
-3
lines changed

4 files changed

+100
-3
lines changed

global.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ declare global {
1111
clientSideEncryption?: boolean;
1212
serverless?: 'forbid' | 'allow' | 'require';
1313
auth?: 'enabled' | 'disabled';
14+
nodejs?: string;
1415
};
1516

1617
sessions?: {

src/error.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ export interface ErrorDescription extends Document {
107107
errInfo?: Document;
108108
}
109109

110+
function isAggregateError(e: Error): e is Error & { errors: Error[] } {
111+
return 'errors' in e && Array.isArray(e.errors);
112+
}
113+
110114
/**
111115
* @public
112116
* @category Error
@@ -130,15 +134,28 @@ export class MongoError extends Error {
130134
cause?: Error; // depending on the node version, this may or may not exist on the base
131135

132136
constructor(message: string | Error) {
137+
super(MongoError.buildErrorMessage(message));
133138
if (message instanceof Error) {
134-
super(message.message);
135139
this.cause = message;
136-
} else {
137-
super(message);
138140
}
141+
139142
this[kErrorLabels] = new Set();
140143
}
141144

145+
/** @internal */
146+
private static buildErrorMessage(e: Error | string): string {
147+
if (typeof e === 'string') {
148+
return e;
149+
}
150+
if (isAggregateError(e) && e.message.length === 0) {
151+
return e.errors.length === 0
152+
? 'AggregateError has an empty errors array. Please check the `cause` property for more information.'
153+
: e.errors.map(({ message }) => message).join(', ');
154+
}
155+
156+
return e.message;
157+
}
158+
142159
override get name(): string {
143160
return 'MongoError';
144161
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { expect } from 'chai';
2+
3+
import { MongoClient, MongoError, MongoServerSelectionError } from '../../../src';
4+
5+
describe('Error (Integration)', function () {
6+
describe('AggregateErrors', function () {
7+
for (const { errors, message } of [
8+
{
9+
errors: [],
10+
message:
11+
'AggregateError has an empty errors array. Please check the `cause` property for more information.'
12+
},
13+
{ errors: [new Error('message 1')], message: 'message 1' },
14+
{
15+
errors: [new Error('message 1'), new Error('message 2')],
16+
message: 'message 1, message 2'
17+
}
18+
]) {
19+
it(
20+
`constructs the message properly with an array of ${errors.length} errors`,
21+
{ requires: { nodejs: '>=16' } },
22+
() => {
23+
const error = new AggregateError(errors);
24+
const mongoError = new MongoError(error);
25+
26+
expect(mongoError.message).to.equal(message);
27+
}
28+
);
29+
}
30+
31+
context('when the message on the AggregateError is non-empty', () => {
32+
it(`uses the AggregateError's message`, { requires: { nodejs: '>=16' } }, () => {
33+
const error = new AggregateError([new Error('non-empty')]);
34+
error.message = 'custom error message';
35+
const mongoError = new MongoError(error);
36+
expect(mongoError.message).to.equal('custom error message');
37+
});
38+
});
39+
40+
it('sets the AggregateError to the cause property', { requires: { nodejs: '>=16' } }, () => {
41+
const error = new AggregateError([new Error('error 1')]);
42+
const mongoError = new MongoError(error);
43+
expect(mongoError.cause).to.equal(error);
44+
});
45+
});
46+
47+
it('NODE-5296: handles aggregate errors from dns lookup', async function () {
48+
const error = await MongoClient.connect('mongodb://localhost:27222', {
49+
serverSelectionTimeoutMS: 1000
50+
}).catch(e => e);
51+
expect(error).to.be.instanceOf(MongoServerSelectionError);
52+
expect(error.message).not.to.be.empty;
53+
});
54+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
'use strict';
2+
3+
const { satisfies } = require('semver');
4+
5+
/**
6+
* Filter for specific nodejs versions
7+
*
8+
* example:
9+
* metadata: {
10+
* requires: {
11+
* nodejs: '>=14'
12+
* }
13+
* }
14+
*/
15+
class NodeVersionFilter {
16+
filter(test) {
17+
if (!test.metadata) return true;
18+
if (!test.metadata.requires) return true;
19+
if (!test.metadata.requires.nodejs) return true;
20+
21+
return satisfies(process.version, test.metadata.requires.nodejs);
22+
}
23+
}
24+
25+
module.exports = NodeVersionFilter;

0 commit comments

Comments
 (0)