Skip to content
14 changes: 8 additions & 6 deletions src/cmap/handshake/client_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class LimitedSizeDocument {
}
}

type MakeClientMetadataOptions = Pick<MongoOptions, 'appName' | 'additionalDriverInfo'>;
type MakeClientMetadataOptions = Pick<MongoOptions, 'appName'>;
/**
* From the specs:
* Implementors SHOULD cumulatively update fields in the following order until the document is under the size limit:
Expand All @@ -116,15 +116,17 @@ type MakeClientMetadataOptions = Pick<MongoOptions, 'appName' | 'additionalDrive
* 3. Omit the `env` document entirely.
* 4. Truncate `platform`. -- special we do not truncate this field
*/
export function makeClientMetadata(options: MakeClientMetadataOptions): ClientMetadata {
export function makeClientMetadata(
driverInfos: DriverInfo[],
{ appName = '' }: MakeClientMetadataOptions
): ClientMetadata {
const metadataDocument = new LimitedSizeDocument(512);

const { appName = '' } = options;
// Add app name first, it must be sent
if (appName.length > 0) {
const name =
Buffer.byteLength(appName, 'utf8') <= 128
? options.appName
? appName
: Buffer.from(appName, 'utf8').subarray(0, 128).toString('utf8');
metadataDocument.ifItFitsItSits('application', { name });
}
Expand All @@ -135,7 +137,7 @@ export function makeClientMetadata(options: MakeClientMetadataOptions): ClientMe
};

// This is where we handle additional driver info added after client construction.
for (const { name: n = '', version: v = '' } of options.additionalDriverInfo) {
for (const { name: n = '', version: v = '' } of driverInfos) {
if (n.length > 0) {
driverInfo.name = `${driverInfo.name}|${n}`;
}
Expand All @@ -152,7 +154,7 @@ export function makeClientMetadata(options: MakeClientMetadataOptions): ClientMe

let runtimeInfo = getRuntimeInfo();
// This is where we handle additional driver info added after client construction.
for (const { platform = '' } of options.additionalDriverInfo) {
for (const { platform = '' } of driverInfos) {
if (platform.length > 0) {
runtimeInfo = `${runtimeInfo}|${platform}`;
}
Expand Down
9 changes: 5 additions & 4 deletions src/mongo_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,13 +431,14 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
| 'extendedMetadata'
>;

private driverInfos: DriverInfo[] = [];

constructor(url: string, options?: MongoClientOptions) {
super();
this.on('error', noop);

this.options = parseOptions(url, this, options);

this.options.additionalDriverInfo = [];
this.appendMetadata(this.options.driverInfo);

const shouldSetLogger = Object.values(this.options.mongoLoggerOptions.componentSeverities).some(
Expand Down Expand Up @@ -496,13 +497,13 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
* @param driverInfo - Information about the application or library.
*/
appendMetadata(driverInfo: DriverInfo) {
const isDuplicateDriverInfo = this.options.additionalDriverInfo.some(info =>
const isDuplicateDriverInfo = this.driverInfos.some(info =>
isDriverInfoEqual(info, driverInfo)
);
if (isDuplicateDriverInfo) return;

this.options.additionalDriverInfo.push(driverInfo);
this.options.metadata = makeClientMetadata(this.options);
this.driverInfos.push(driverInfo);
this.options.metadata = makeClientMetadata(this.driverInfos, this.options);
this.options.extendedMetadata = addContainerMetadata(this.options.metadata)
.then(undefined, squashError)
.then(result => result ?? {}); // ensure Promise<Document>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,8 @@ describe('Connection', function () {
...commonConnectOptions,
connectionType: Connection,
...this.configuration.options,
metadata: makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] }),
extendedMetadata: addContainerMetadata(
makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] })
)
metadata: makeClientMetadata([], {}),
extendedMetadata: addContainerMetadata(makeClientMetadata([], {}))
};

let conn;
Expand All @@ -74,10 +72,8 @@ describe('Connection', function () {
connectionType: Connection,
...this.configuration.options,
monitorCommands: true,
metadata: makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] }),
extendedMetadata: addContainerMetadata(
makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] })
)
metadata: makeClientMetadata([], {}),
extendedMetadata: addContainerMetadata(makeClientMetadata([], {}))
};

let conn;
Expand Down Expand Up @@ -108,10 +104,8 @@ describe('Connection', function () {
connectionType: Connection,
...this.configuration.options,
monitorCommands: true,
metadata: makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] }),
extendedMetadata: addContainerMetadata(
makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] })
)
metadata: makeClientMetadata([], {}),
extendedMetadata: addContainerMetadata(makeClientMetadata([], {}))
};

let conn;
Expand Down
100 changes: 40 additions & 60 deletions test/unit/cmap/handshake/client_metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ describe('client metadata module', () => {
describe('makeClientMetadata()', () => {
context('when no FAAS environment is detected', () => {
it('does not append FAAS metadata', () => {
const metadata = makeClientMetadata({ additionalDriverInfo: [{}] });
const metadata = makeClientMetadata([], {});
expect(metadata).not.to.have.property(
'env',
'faas metadata applied in a non-faas environment'
Expand All @@ -165,18 +165,14 @@ describe('client metadata module', () => {

context('when driverInfo.platform is provided', () => {
it('throws an error if driverInfo.platform is too large', () => {
expect(() =>
makeClientMetadata({
additionalDriverInfo: [{ platform: 'a'.repeat(512) }]
})
).to.throw(MongoInvalidArgumentError, /platform/);
expect(() => makeClientMetadata([{ platform: 'a'.repeat(512) }], {})).to.throw(
MongoInvalidArgumentError,
/platform/
);
});

it('appends driverInfo.platform to the platform field', () => {
const options = {
additionalDriverInfo: [{ platform: 'myPlatform' }]
};
const metadata = makeClientMetadata(options);
const metadata = makeClientMetadata([{ platform: 'myPlatform' }], {});
expect(metadata).to.deep.equal({
driver: {
name: 'nodejs',
Expand All @@ -195,13 +191,14 @@ describe('client metadata module', () => {

context('when driverInfo.name is provided', () => {
it('throws an error if driverInfo.name is too large', () => {
expect(() =>
makeClientMetadata({ additionalDriverInfo: [{ name: 'a'.repeat(512) }] })
).to.throw(MongoInvalidArgumentError, /name/);
expect(() => makeClientMetadata([{ name: 'a'.repeat(512) }], {})).to.throw(
MongoInvalidArgumentError,
/name/
);
});

it('appends driverInfo.name to the driver.name field', () => {
const metadata = makeClientMetadata({ additionalDriverInfo: [{ name: 'myName' }] });
const metadata = makeClientMetadata([{ name: 'myName' }], {});
expect(metadata).to.deep.equal({
driver: {
name: 'nodejs|myName',
Expand All @@ -220,15 +217,14 @@ describe('client metadata module', () => {

context('when driverInfo.version is provided', () => {
it('throws an error if driverInfo.version is too large', () => {
expect(() =>
makeClientMetadata({ additionalDriverInfo: [{ version: 'a'.repeat(512) }] })
).to.throw(MongoInvalidArgumentError, /version/);
expect(() => makeClientMetadata([{ version: 'a'.repeat(512) }], {})).to.throw(
MongoInvalidArgumentError,
/version/
);
});

it('appends driverInfo.version to the version field', () => {
const metadata = makeClientMetadata({
additionalDriverInfo: [{ version: 'myVersion' }]
});
const metadata = makeClientMetadata([{ version: 'myVersion' }], {});
expect(metadata).to.deep.equal({
driver: {
name: 'nodejs',
Expand All @@ -246,7 +242,7 @@ describe('client metadata module', () => {
});

context('when no custom driverInto is provided', () => {
const metadata = makeClientMetadata({ additionalDriverInfo: [] });
const metadata = makeClientMetadata([], {});

it('does not append the driver info to the metadata', () => {
expect(metadata).to.deep.equal({
Expand All @@ -272,9 +268,8 @@ describe('client metadata module', () => {
context('when app name is provided', () => {
context('when the app name is over 128 bytes', () => {
const longString = 'a'.repeat(300);
const metadata = makeClientMetadata({
appName: longString,
additionalDriverInfo: []
const metadata = makeClientMetadata([], {
appName: longString
});

it('truncates the application name to <=128 bytes', () => {
Expand All @@ -290,9 +285,8 @@ describe('client metadata module', () => {
'TODO(NODE-5150): fix appName truncation when multi-byte unicode charaters straddle byte 128',
() => {
const longString = '€'.repeat(300);
const metadata = makeClientMetadata({
appName: longString,
additionalDriverInfo: []
const metadata = makeClientMetadata([], {
appName: longString
});

it('truncates the application name to 129 bytes', () => {
Expand All @@ -306,9 +300,8 @@ describe('client metadata module', () => {
);

context('when the app name is under 128 bytes', () => {
const metadata = makeClientMetadata({
appName: 'myApplication',
additionalDriverInfo: []
const metadata = makeClientMetadata([], {
appName: 'myApplication'
});

it('sets the application name to the value', () => {
Expand All @@ -325,39 +318,37 @@ describe('client metadata module', () => {

it('sets platform to Deno', () => {
globalThis.Deno = { version: { deno: '1.2.3' } };
const metadata = makeClientMetadata({ additionalDriverInfo: [] });
const metadata = makeClientMetadata([], {});
expect(metadata.platform).to.equal('Deno v1.2.3, LE');
});

it('sets platform to Deno with driverInfo.platform', () => {
globalThis.Deno = { version: { deno: '1.2.3' } };
const metadata = makeClientMetadata({
additionalDriverInfo: [{ platform: 'myPlatform' }]
});
const metadata = makeClientMetadata([{ platform: 'myPlatform' }], {});
expect(metadata.platform).to.equal('Deno v1.2.3, LE|myPlatform');
});

it('ignores version if Deno.version.deno is not a string', () => {
globalThis.Deno = { version: { deno: 1 } };
const metadata = makeClientMetadata({ additionalDriverInfo: [] });
const metadata = makeClientMetadata([], {});
expect(metadata.platform).to.equal('Deno v0.0.0-unknown, LE');
});

it('ignores version if Deno.version does not have a deno property', () => {
globalThis.Deno = { version: { somethingElse: '1.2.3' } };
const metadata = makeClientMetadata({ additionalDriverInfo: [] });
const metadata = makeClientMetadata([], {});
expect(metadata.platform).to.equal('Deno v0.0.0-unknown, LE');
});

it('ignores version if Deno.version is null', () => {
globalThis.Deno = { version: null };
const metadata = makeClientMetadata({ additionalDriverInfo: [] });
const metadata = makeClientMetadata([], {});
expect(metadata.platform).to.equal('Deno v0.0.0-unknown, LE');
});

it('ignores version if Deno is nullish', () => {
globalThis.Deno = null;
const metadata = makeClientMetadata({ additionalDriverInfo: [] });
const metadata = makeClientMetadata([], {});
expect(metadata.platform).to.equal('Deno v0.0.0-unknown, LE');
});
});
Expand All @@ -371,43 +362,37 @@ describe('client metadata module', () => {
globalThis.Bun = class {
static version = '1.2.3';
};
const metadata = makeClientMetadata({ additionalDriverInfo: [] });
const metadata = makeClientMetadata([], {});
expect(metadata.platform).to.equal('Bun v1.2.3, LE');
});

it('sets platform to Bun with driverInfo.platform', () => {
globalThis.Bun = class {
static version = '1.2.3';
};
const metadata = makeClientMetadata({
additionalDriverInfo: [{ platform: 'myPlatform' }]
});
const metadata = makeClientMetadata([{ platform: 'myPlatform' }], {});
expect(metadata.platform).to.equal('Bun v1.2.3, LE|myPlatform');
});

it('ignores version if Bun.version is not a string', () => {
globalThis.Bun = class {
static version = 1;
};
const metadata = makeClientMetadata({ additionalDriverInfo: [] });
const metadata = makeClientMetadata([], {});
expect(metadata.platform).to.equal('Bun v0.0.0-unknown, LE');
});

it('ignores version if Bun.version is not a string and sets driverInfo.platform', () => {
globalThis.Bun = class {
static version = 1;
};
const metadata = makeClientMetadata({
additionalDriverInfo: [{ platform: 'myPlatform' }]
});
const metadata = makeClientMetadata([{ platform: 'myPlatform' }], {});
expect(metadata.platform).to.equal('Bun v0.0.0-unknown, LE|myPlatform');
});

it('ignores version if Bun is nullish', () => {
globalThis.Bun = null;
const metadata = makeClientMetadata({
additionalDriverInfo: [{ platform: 'myPlatform' }]
});
const metadata = makeClientMetadata([{ platform: 'myPlatform' }], {});
expect(metadata.platform).to.equal('Bun v0.0.0-unknown, LE|myPlatform');
});
});
Expand Down Expand Up @@ -528,7 +513,7 @@ describe('client metadata module', () => {
});

it(`returns ${inspect(outcome)} under env property`, () => {
const { env } = makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] });
const { env } = makeClientMetadata([], {});
expect(env).to.deep.equal(outcome);
});

Expand All @@ -552,9 +537,7 @@ describe('client metadata module', () => {
});

it('does not attach it to the metadata', () => {
expect(
makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] })
).not.to.have.nested.property('aws.memory_mb');
expect(makeClientMetadata([], {})).not.to.have.nested.property('aws.memory_mb');
});
});
});
Expand All @@ -569,7 +552,7 @@ describe('client metadata module', () => {
});

it('only includes env.name', () => {
const metadata = makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] });
const metadata = makeClientMetadata([], {});
expect(metadata).to.not.have.nested.property('env.region');
expect(metadata).to.have.nested.property('env.name', 'aws.lambda');
expect(metadata.env).to.have.all.keys('name');
Expand All @@ -587,7 +570,7 @@ describe('client metadata module', () => {
});

it('only includes env.name', () => {
const metadata = makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] });
const metadata = makeClientMetadata([], {});
expect(metadata).to.have.property('env');
expect(metadata).to.have.nested.property('env.region', 'abc');
expect(metadata.os).to.have.all.keys('type');
Expand All @@ -604,7 +587,7 @@ describe('client metadata module', () => {
});

it('omits os information', () => {
const metadata = makeClientMetadata({ driverInfo: {}, additionalDriverInfo: [] });
const metadata = makeClientMetadata([], {});
expect(metadata).to.not.have.property('os');
});
});
Expand All @@ -620,10 +603,7 @@ describe('client metadata module', () => {
});

it('omits the faas env', () => {
const metadata = makeClientMetadata({
driverInfo: { name: 'a'.repeat(350) },
additionalDriverInfo: []
});
const metadata = makeClientMetadata([{ name: 'a'.repeat(350) }], {});
expect(metadata).to.not.have.property('env');
});
});
Expand Down
Loading