Skip to content

Commit 4f56409

Browse files
feat(NODE-3784): Add enableUtf8Validation option (#3074)
Co-authored-by: Bailey Pearson <bailey.pearson@mongodb.com>
1 parent ea1f1f9 commit 4f56409

File tree

11 files changed

+222
-68
lines changed

11 files changed

+222
-68
lines changed

src/bson.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ export interface BSONSerializeOptions
5555
> {
5656
/** Return BSON filled buffers from operations */
5757
raw?: boolean;
58+
59+
/** Enable utf8 validation when deserializing BSON documents. Defaults to true. */
60+
enableUtf8Validation?: boolean;
5861
}
5962

6063
export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSerializeOptions {
@@ -66,7 +69,8 @@ export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSe
6669
serializeFunctions,
6770
ignoreUndefined,
6871
bsonRegExp,
69-
raw
72+
raw,
73+
enableUtf8Validation
7074
} = options;
7175
return {
7276
fieldsAsRaw,
@@ -76,7 +80,8 @@ export function pluckBSONSerializeOptions(options: BSONSerializeOptions): BSONSe
7680
serializeFunctions,
7781
ignoreUndefined,
7882
bsonRegExp,
79-
raw
83+
raw,
84+
enableUtf8Validation
8085
};
8186
}
8287

@@ -99,6 +104,8 @@ export function resolveBSONOptions(
99104
ignoreUndefined: options?.ignoreUndefined ?? parentOptions?.ignoreUndefined ?? false,
100105
bsonRegExp: options?.bsonRegExp ?? parentOptions?.bsonRegExp ?? false,
101106
serializeFunctions: options?.serializeFunctions ?? parentOptions?.serializeFunctions ?? false,
102-
fieldsAsRaw: options?.fieldsAsRaw ?? parentOptions?.fieldsAsRaw ?? {}
107+
fieldsAsRaw: options?.fieldsAsRaw ?? parentOptions?.fieldsAsRaw ?? {},
108+
enableUtf8Validation:
109+
options?.enableUtf8Validation ?? parentOptions?.enableUtf8Validation ?? true
103110
};
104111
}

src/cmap/commands.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,8 +469,6 @@ export interface MessageHeader {
469469
export interface OpResponseOptions extends BSONSerializeOptions {
470470
raw?: boolean;
471471
documentsReturnedIn?: string | null;
472-
// For now we use this internally to only prevent writeErrors from crashing the driver
473-
validation?: { utf8: { writeErrors: boolean } };
474472
}
475473

476474
/** @internal */
@@ -839,7 +837,7 @@ export class BinMsg {
839837
const promoteValues = options.promoteValues ?? this.opts.promoteValues;
840838
const promoteBuffers = options.promoteBuffers ?? this.opts.promoteBuffers;
841839
const bsonRegExp = options.bsonRegExp ?? this.opts.bsonRegExp;
842-
const validation = options.validation ?? { utf8: { writeErrors: false } };
840+
const validation = this.parseBsonSerializationOptions(options);
843841

844842
// Set up the options
845843
const bsonOptions: BSONSerializeOptions = {
@@ -876,4 +874,14 @@ export class BinMsg {
876874

877875
this.parsed = true;
878876
}
877+
878+
parseBsonSerializationOptions({ enableUtf8Validation }: BSONSerializeOptions): {
879+
utf8: { writeErrors: false } | false;
880+
} {
881+
if (enableUtf8Validation === false) {
882+
return { utf8: false };
883+
}
884+
885+
return { utf8: { writeErrors: false } };
886+
}
879887
}

src/cmap/connection.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,8 @@ function write(
787787
promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
788788
promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,
789789
bsonRegExp: typeof options.bsonRegExp === 'boolean' ? options.bsonRegExp : false,
790+
enableUtf8Validation:
791+
typeof options.enableUtf8Validation === 'boolean' ? options.enableUtf8Validation : true,
790792
raw: typeof options.raw === 'boolean' ? options.raw : false,
791793
started: 0
792794
};

src/cmap/wire_protocol/shared.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ export function applyCommonQueryOptions(
4545
promoteLongs: typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true,
4646
promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
4747
promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,
48-
bsonRegExp: typeof options.bsonRegExp === 'boolean' ? options.bsonRegExp : false
48+
bsonRegExp: typeof options.bsonRegExp === 'boolean' ? options.bsonRegExp : false,
49+
enableUtf8Validation:
50+
typeof options.enableUtf8Validation === 'boolean' ? options.enableUtf8Validation : true
4951
});
5052

5153
if (options.session) {

src/connection_string.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,7 @@ export const OPTIONS = {
688688
});
689689
}
690690
},
691+
enableUtf8Validation: { type: 'boolean', default: true },
691692
family: {
692693
transform({ name, values: [value] }): 4 | 6 {
693694
const transformValue = getInt(name, value);

src/db.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const DB_OPTIONS_ALLOW_LIST = [
7373
'promoteBuffers',
7474
'promoteLongs',
7575
'bsonRegExp',
76+
'enableUtf8Validation',
7677
'promoteValues',
7778
'compression',
7879
'retryWrites'

src/operations/create_collection.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ const ILLEGAL_COMMAND_FIELDS = new Set([
2727
'promoteBuffers',
2828
'bsonRegExp',
2929
'serializeFunctions',
30-
'ignoreUndefined'
30+
'ignoreUndefined',
31+
'enableUtf8Validation'
3132
]);
3233

3334
/** @public

src/operations/map_reduce.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const exclusionList = [
3333
'bsonRegExp',
3434
'serializeFunctions',
3535
'ignoreUndefined',
36+
'enableUtf8Validation',
3637
'scope' // this option is reformatted thus exclude the original
3738
];
3839

test/functional/cmap/commands.test.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { spy } from 'sinon';
2+
import { expect } from 'chai';
3+
import * as BSON from '../../../src/bson';
4+
5+
const deserializeSpy = spy(BSON, 'deserialize');
6+
7+
const EXPECTED_VALIDATION_DISABLED_ARGUMENT = {
8+
utf8: false
9+
};
10+
11+
const EXPECTED_VALIDATION_ENABLED_ARGUMENT = {
12+
utf8: {
13+
writeErrors: false
14+
}
15+
};
16+
17+
describe('class BinMsg', () => {
18+
beforeEach(() => {
19+
deserializeSpy.resetHistory();
20+
});
21+
22+
describe('enableUtf8Validation option set to false', () => {
23+
let client;
24+
const option = { enableUtf8Validation: false };
25+
26+
for (const passOptionTo of ['client', 'db', 'collection', 'operation']) {
27+
it(`should disable validation with option passed to ${passOptionTo}`, async function () {
28+
try {
29+
client = this.configuration.newClient(passOptionTo === 'client' ? option : undefined);
30+
await client.connect();
31+
32+
const db = client.db(
33+
'bson_utf8Validation_db',
34+
passOptionTo === 'db' ? option : undefined
35+
);
36+
const collection = db.collection(
37+
'bson_utf8Validation_coll',
38+
passOptionTo === 'collection' ? option : undefined
39+
);
40+
41+
await collection.insertOne(
42+
{ name: 'John Doe' },
43+
passOptionTo === 'operation' ? option : {}
44+
);
45+
46+
expect(deserializeSpy.called).to.be.true;
47+
const validationArgument = deserializeSpy.lastCall.lastArg.validation;
48+
expect(validationArgument).to.deep.equal(EXPECTED_VALIDATION_DISABLED_ARGUMENT);
49+
} finally {
50+
await client.close();
51+
}
52+
});
53+
}
54+
});
55+
56+
describe('enableUtf8Validation option set to true', () => {
57+
// define client and option for tests to use
58+
let client;
59+
const option = { enableUtf8Validation: true };
60+
for (const passOptionTo of ['client', 'db', 'collection', 'operation']) {
61+
it(`should enable validation with option passed to ${passOptionTo}`, async function () {
62+
try {
63+
client = this.configuration.newClient(passOptionTo === 'client' ? option : undefined);
64+
await client.connect();
65+
66+
const db = client.db(
67+
'bson_utf8Validation_db',
68+
passOptionTo === 'db' ? option : undefined
69+
);
70+
const collection = db.collection(
71+
'bson_utf8Validation_coll',
72+
passOptionTo === 'collection' ? option : undefined
73+
);
74+
75+
await collection.insertOne(
76+
{ name: 'John Doe' },
77+
passOptionTo === 'operation' ? option : {}
78+
);
79+
80+
expect(deserializeSpy.called).to.be.true;
81+
const validationArgument = deserializeSpy.lastCall.lastArg.validation;
82+
expect(validationArgument).to.deep.equal(EXPECTED_VALIDATION_ENABLED_ARGUMENT);
83+
} finally {
84+
await client.close();
85+
}
86+
});
87+
}
88+
});
89+
90+
describe('enableUtf8Validation option not set', () => {
91+
let client;
92+
const option = { enableUtf8Validation: true };
93+
for (const passOptionTo of ['client', 'db', 'collection', 'operation']) {
94+
it(`should default to enabled with option passed to ${passOptionTo}`, async function () {
95+
try {
96+
client = this.configuration.newClient(passOptionTo === 'client' ? option : undefined);
97+
await client.connect();
98+
99+
const db = client.db(
100+
'bson_utf8Validation_db',
101+
passOptionTo === 'db' ? option : undefined
102+
);
103+
const collection = db.collection(
104+
'bson_utf8Validation_coll',
105+
passOptionTo === 'collection' ? option : undefined
106+
);
107+
108+
await collection.insertOne(
109+
{ name: 'John Doe' },
110+
passOptionTo === 'operation' ? option : {}
111+
);
112+
113+
expect(deserializeSpy.called).to.be.true;
114+
const validationArgument = deserializeSpy.lastCall.lastArg.validation;
115+
expect(validationArgument).to.deep.equal(EXPECTED_VALIDATION_ENABLED_ARGUMENT);
116+
} finally {
117+
await client.close();
118+
}
119+
});
120+
}
121+
});
122+
});

test/types/bson.test-d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type PermittedBSONOptionKeys =
2121
| 'promoteValues'
2222
| 'bsonRegExp'
2323
| 'fieldsAsRaw'
24+
| 'enableUtf8Validation'
2425
| 'raw';
2526

2627
const keys = null as unknown as PermittedBSONOptionKeys;

0 commit comments

Comments
 (0)