Skip to content

Commit

Permalink
feat(NODE-3784): Add enableUtf8Validation option (#3074)
Browse files Browse the repository at this point in the history
Co-authored-by: Bailey Pearson <bailey.pearson@mongodb.com>
  • Loading branch information
durran and baileympearson committed Dec 13, 2021
1 parent ea1f1f9 commit 4f56409
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 68 deletions.
13 changes: 10 additions & 3 deletions src/bson.ts
Expand Up @@ -55,6 +55,9 @@ export interface BSONSerializeOptions
> {
/** Return BSON filled buffers from operations */
raw?: boolean;

/** Enable utf8 validation when deserializing BSON documents. Defaults to true. */
enableUtf8Validation?: boolean;
}

export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSerializeOptions {
Expand All @@ -66,7 +69,8 @@ export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSe
serializeFunctions,
ignoreUndefined,
bsonRegExp,
raw
raw,
enableUtf8Validation
} = options;
return {
fieldsAsRaw,
Expand All @@ -76,7 +80,8 @@ export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSe
serializeFunctions,
ignoreUndefined,
bsonRegExp,
raw
raw,
enableUtf8Validation
};
}

Expand All @@ -99,6 +104,8 @@ export function resolveBSONOptions(
ignoreUndefined: options?.ignoreUndefined ?? parentOptions?.ignoreUndefined ?? false,
bsonRegExp: options?.bsonRegExp ?? parentOptions?.bsonRegExp ?? false,
serializeFunctions: options?.serializeFunctions ?? parentOptions?.serializeFunctions ?? false,
fieldsAsRaw: options?.fieldsAsRaw ?? parentOptions?.fieldsAsRaw ?? {}
fieldsAsRaw: options?.fieldsAsRaw ?? parentOptions?.fieldsAsRaw ?? {},
enableUtf8Validation:
options?.enableUtf8Validation ?? parentOptions?.enableUtf8Validation ?? true
};
}
14 changes: 11 additions & 3 deletions src/cmap/commands.ts
Expand Up @@ -469,8 +469,6 @@ export interface MessageHeader {
export interface OpResponseOptions extends BSONSerializeOptions {
raw?: boolean;
documentsReturnedIn?: string | null;
// For now we use this internally to only prevent writeErrors from crashing the driver
validation?: { utf8: { writeErrors: boolean } };
}

/** @internal */
Expand Down Expand Up @@ -839,7 +837,7 @@ export class BinMsg {
const promoteValues = options.promoteValues ?? this.opts.promoteValues;
const promoteBuffers = options.promoteBuffers ?? this.opts.promoteBuffers;
const bsonRegExp = options.bsonRegExp ?? this.opts.bsonRegExp;
const validation = options.validation ?? { utf8: { writeErrors: false } };
const validation = this.parseBsonSerializationOptions(options);

// Set up the options
const bsonOptions: BSONSerializeOptions = {
Expand Down Expand Up @@ -876,4 +874,14 @@ export class BinMsg {

this.parsed = true;
}

parseBsonSerializationOptions({ enableUtf8Validation }: BSONSerializeOptions): {
utf8: { writeErrors: false } | false;
} {
if (enableUtf8Validation === false) {
return { utf8: false };
}

return { utf8: { writeErrors: false } };
}
}
2 changes: 2 additions & 0 deletions src/cmap/connection.ts
Expand Up @@ -787,6 +787,8 @@ function write(
promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,
bsonRegExp: typeof options.bsonRegExp === 'boolean' ? options.bsonRegExp : false,
enableUtf8Validation:
typeof options.enableUtf8Validation === 'boolean' ? options.enableUtf8Validation : true,
raw: typeof options.raw === 'boolean' ? options.raw : false,
started: 0
};
Expand Down
4 changes: 3 additions & 1 deletion src/cmap/wire_protocol/shared.ts
Expand Up @@ -45,7 +45,9 @@ export function applyCommonQueryOptions(
promoteLongs: typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true,
promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,
bsonRegExp: typeof options.bsonRegExp === 'boolean' ? options.bsonRegExp : false
bsonRegExp: typeof options.bsonRegExp === 'boolean' ? options.bsonRegExp : false,
enableUtf8Validation:
typeof options.enableUtf8Validation === 'boolean' ? options.enableUtf8Validation : true
});

if (options.session) {
Expand Down
1 change: 1 addition & 0 deletions src/connection_string.ts
Expand Up @@ -688,6 +688,7 @@ export const OPTIONS = {
});
}
},
enableUtf8Validation: { type: 'boolean', default: true },
family: {
transform({ name, values: [value] }): 4 | 6 {
const transformValue = getInt(name, value);
Expand Down
1 change: 1 addition & 0 deletions src/db.ts
Expand Up @@ -73,6 +73,7 @@ const DB_OPTIONS_ALLOW_LIST = [
'promoteBuffers',
'promoteLongs',
'bsonRegExp',
'enableUtf8Validation',
'promoteValues',
'compression',
'retryWrites'
Expand Down
3 changes: 2 additions & 1 deletion src/operations/create_collection.ts
Expand Up @@ -27,7 +27,8 @@ const ILLEGAL_COMMAND_FIELDS = new Set([
'promoteBuffers',
'bsonRegExp',
'serializeFunctions',
'ignoreUndefined'
'ignoreUndefined',
'enableUtf8Validation'
]);

/** @public
Expand Down
1 change: 1 addition & 0 deletions src/operations/map_reduce.ts
Expand Up @@ -33,6 +33,7 @@ const exclusionList = [
'bsonRegExp',
'serializeFunctions',
'ignoreUndefined',
'enableUtf8Validation',
'scope' // this option is reformatted thus exclude the original
];

Expand Down
122 changes: 122 additions & 0 deletions test/functional/cmap/commands.test.ts
@@ -0,0 +1,122 @@
import { spy } from 'sinon';
import { expect } from 'chai';
import * as BSON from '../../../src/bson';

const deserializeSpy = spy(BSON, 'deserialize');

const EXPECTED_VALIDATION_DISABLED_ARGUMENT = {
utf8: false
};

const EXPECTED_VALIDATION_ENABLED_ARGUMENT = {
utf8: {
writeErrors: false
}
};

describe('class BinMsg', () => {
beforeEach(() => {
deserializeSpy.resetHistory();
});

describe('enableUtf8Validation option set to false', () => {
let client;
const option = { enableUtf8Validation: false };

for (const passOptionTo of ['client', 'db', 'collection', 'operation']) {
it(`should disable validation with option passed to ${passOptionTo}`, async function () {
try {
client = this.configuration.newClient(passOptionTo === 'client' ? option : undefined);
await client.connect();

const db = client.db(
'bson_utf8Validation_db',
passOptionTo === 'db' ? option : undefined
);
const collection = db.collection(
'bson_utf8Validation_coll',
passOptionTo === 'collection' ? option : undefined
);

await collection.insertOne(
{ name: 'John Doe' },
passOptionTo === 'operation' ? option : {}
);

expect(deserializeSpy.called).to.be.true;
const validationArgument = deserializeSpy.lastCall.lastArg.validation;
expect(validationArgument).to.deep.equal(EXPECTED_VALIDATION_DISABLED_ARGUMENT);
} finally {
await client.close();
}
});
}
});

describe('enableUtf8Validation option set to true', () => {
// define client and option for tests to use
let client;
const option = { enableUtf8Validation: true };
for (const passOptionTo of ['client', 'db', 'collection', 'operation']) {
it(`should enable validation with option passed to ${passOptionTo}`, async function () {
try {
client = this.configuration.newClient(passOptionTo === 'client' ? option : undefined);
await client.connect();

const db = client.db(
'bson_utf8Validation_db',
passOptionTo === 'db' ? option : undefined
);
const collection = db.collection(
'bson_utf8Validation_coll',
passOptionTo === 'collection' ? option : undefined
);

await collection.insertOne(
{ name: 'John Doe' },
passOptionTo === 'operation' ? option : {}
);

expect(deserializeSpy.called).to.be.true;
const validationArgument = deserializeSpy.lastCall.lastArg.validation;
expect(validationArgument).to.deep.equal(EXPECTED_VALIDATION_ENABLED_ARGUMENT);
} finally {
await client.close();
}
});
}
});

describe('enableUtf8Validation option not set', () => {
let client;
const option = { enableUtf8Validation: true };
for (const passOptionTo of ['client', 'db', 'collection', 'operation']) {
it(`should default to enabled with option passed to ${passOptionTo}`, async function () {
try {
client = this.configuration.newClient(passOptionTo === 'client' ? option : undefined);
await client.connect();

const db = client.db(
'bson_utf8Validation_db',
passOptionTo === 'db' ? option : undefined
);
const collection = db.collection(
'bson_utf8Validation_coll',
passOptionTo === 'collection' ? option : undefined
);

await collection.insertOne(
{ name: 'John Doe' },
passOptionTo === 'operation' ? option : {}
);

expect(deserializeSpy.called).to.be.true;
const validationArgument = deserializeSpy.lastCall.lastArg.validation;
expect(validationArgument).to.deep.equal(EXPECTED_VALIDATION_ENABLED_ARGUMENT);
} finally {
await client.close();
}
});
}
});
});
1 change: 1 addition & 0 deletions test/types/bson.test-d.ts
Expand Up @@ -21,6 +21,7 @@ type PermittedBSONOptionKeys =
| 'promoteValues'
| 'bsonRegExp'
| 'fieldsAsRaw'
| 'enableUtf8Validation'
| 'raw';

const keys = null as unknown as PermittedBSONOptionKeys;
Expand Down

0 comments on commit 4f56409

Please sign in to comment.