Skip to content

Commit e03178e

Browse files
fix(NODE-5296): construct error messages for AggregateErrors in Node16+ (#3682)
1 parent 9484fd6 commit e03178e

File tree

4 files changed

+101
-3
lines changed

4 files changed

+101
-3
lines changed

global.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ declare global {
1212
serverless?: 'forbid' | 'allow' | 'require';
1313
auth?: 'enabled' | 'disabled';
1414
idmsMockServer?: true;
15+
nodejs?: string;
1516
};
1617

1718
sessions?: {

src/error.ts

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

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

133137
constructor(message: string | Error) {
138+
super(MongoError.buildErrorMessage(message));
134139
if (message instanceof Error) {
135-
super(message.message);
136140
this.cause = message;
137-
} else {
138-
super(message);
139141
}
142+
140143
this[kErrorLabels] = new Set();
141144
}
142145

146+
/** @internal */
147+
private static buildErrorMessage(e: Error | string): string {
148+
if (typeof e === 'string') {
149+
return e;
150+
}
151+
if (isAggregateError(e) && e.message.length === 0) {
152+
return e.errors.length === 0
153+
? 'AggregateError has an empty errors array. Please check the `cause` property for more information.'
154+
: e.errors.map(({ message }) => message).join(', ');
155+
}
156+
157+
return e.message;
158+
}
159+
143160
override get name(): string {
144161
return 'MongoError';
145162
}
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 '../../mongodb';
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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
const nodeVersionRange = test?.metadata?.requires?.nodejs;
18+
if (!nodeVersionRange) {
19+
return true;
20+
}
21+
22+
return satisfies(process.version, nodeVersionRange);
23+
}
24+
}
25+
26+
module.exports = NodeVersionFilter;

0 commit comments

Comments
 (0)