Skip to content

Commit 3a0e011

Browse files
fix(NODE-5530): make topology descriptions JSON stringifiable (#4070)
Co-authored-by: Aditi Khare <[email protected]>
1 parent ddd1e81 commit 3a0e011

File tree

3 files changed

+79
-11
lines changed

3 files changed

+79
-11
lines changed

src/sdam/topology_description.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ObjectId } from '../bson';
1+
import { EJSON, type ObjectId } from '../bson';
22
import * as WIRE_CONSTANTS from '../cmap/wire_protocol/constants';
33
import { type MongoError, MongoRuntimeError } from '../error';
44
import { compareObjectId, shuffle } from '../utils';
@@ -342,6 +342,16 @@ export class TopologyDescription {
342342
hasServer(address: string): boolean {
343343
return this.servers.has(address);
344344
}
345+
346+
/**
347+
* Returns a JSON-serializable representation of the TopologyDescription. This is primarily
348+
* intended for use with JSON.stringify().
349+
*
350+
* This method will not throw.
351+
*/
352+
toJSON() {
353+
return EJSON.serialize(this);
354+
}
345355
}
346356

347357
function topologyTypeForServerType(serverType: ServerType): TopologyType {

test/integration/node-specific/errors.test.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from 'chai';
22

3-
import { MongoClient, MongoServerSelectionError } from '../../mongodb';
3+
import { MongoClient, MongoServerSelectionError, ReadPreference } from '../../mongodb';
44

55
describe('Error (Integration)', function () {
66
it('NODE-5296: handles aggregate errors from dns lookup', async function () {
@@ -10,4 +10,37 @@ describe('Error (Integration)', function () {
1010
expect(error).to.be.instanceOf(MongoServerSelectionError);
1111
expect(error.message).not.to.be.empty;
1212
});
13+
14+
context('when a server selection error is stringified', function () {
15+
it(
16+
'the error"s topology description correctly displays the `servers`',
17+
{ requires: { topology: 'replicaset' } },
18+
async function () {
19+
const client: MongoClient = this.configuration.newClient({
20+
serverSelectionTimeoutMS: 1000
21+
});
22+
try {
23+
await client.connect();
24+
25+
const error = await client
26+
.db('foo')
27+
.collection('bar')
28+
.find(
29+
{},
30+
{
31+
// Use meaningless read preference tags to ensure that the server selection fails
32+
readPreference: new ReadPreference('secondary', [{ ny: 'ny' }])
33+
}
34+
)
35+
.toArray()
36+
.catch(e => JSON.parse(JSON.stringify(e)));
37+
38+
const servers = error.reason.servers;
39+
expect(Object.keys(servers).length > 0).to.be.true;
40+
} finally {
41+
await client.close();
42+
}
43+
}
44+
);
45+
});
1346
});

test/integration/server-discovery-and-monitoring/topology_description.test.ts

+34-9
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,22 @@ describe('TopologyDescription (integration tests)', function () {
1414
await client.close();
1515
});
1616

17+
beforeEach(async function () {
18+
client = this.configuration.newClient();
19+
await client.connect();
20+
});
21+
1722
context('options', function () {
23+
let client: MongoClient;
24+
25+
afterEach(async function () {
26+
await client.close();
27+
});
28+
29+
beforeEach(async function () {
30+
client = this.configuration.newClient();
31+
});
32+
1833
context('localThresholdMS', function () {
1934
it('should default to 15ms', async function () {
2035
const options: MongoClientOptions = {};
@@ -35,15 +50,6 @@ describe('TopologyDescription (integration tests)', function () {
3550
});
3651

3752
context('topology types', function () {
38-
let client: MongoClient;
39-
beforeEach(async function () {
40-
client = this.configuration.newClient();
41-
});
42-
43-
afterEach(async function () {
44-
await client.close();
45-
});
46-
4753
const topologyTypesMap = new Map<TopologyTypeRequirement, TopologyType>([
4854
['single', TopologyType.Single],
4955
['replicaset', TopologyType.ReplicaSetWithPrimary],
@@ -65,4 +71,23 @@ describe('TopologyDescription (integration tests)', function () {
6571
);
6672
}
6773
});
74+
75+
describe('json stringification', function () {
76+
it('can be stringified without error', function () {
77+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
78+
const description = client.topology?.description!;
79+
expect(description).to.exist;
80+
81+
expect(() => JSON.stringify(description)).not.to.throw;
82+
});
83+
84+
it('properly stringifies the server description map', function () {
85+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
86+
const description = client.topology?.description!;
87+
expect(description).to.exist;
88+
89+
const { servers } = JSON.parse(JSON.stringify(description));
90+
expect(Object.keys(servers).length > 0, '`servers` stringified with no servers.').to.be.true;
91+
});
92+
});
6893
});

0 commit comments

Comments
 (0)