Skip to content

fix: Fix backwards compatibility with older BSON package versions #411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 38 additions & 20 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@babel/plugin-external-helpers": "^7.10.4",
"@babel/preset-env": "^7.11.0",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@microsoft/api-extractor": "^7.9.10",
"@microsoft/api-extractor": "^7.11.2",
"@rollup/plugin-babel": "^5.2.0",
"@rollup/plugin-commonjs": "^15.0.0",
"@rollup/plugin-json": "^4.1.0",
Expand All @@ -49,6 +49,7 @@
"karma-mocha-reporter": "^2.2.5",
"karma-rollup-preprocessor": "^7.0.5",
"mocha": "5.2.0",
"node-fetch": "^2.6.1",
"nyc": "^15.1.0",
"prettier": "^2.1.1",
"rimraf": "^3.0.2",
Expand Down Expand Up @@ -78,7 +79,7 @@
"scripts": {
"docs": "typedoc",
"test": "npm run build && npm run test-node && npm run test-browser",
"test-node": "mocha test/node",
"test-node": "mocha test/node test/*_tests.js",
"test-browser": "karma start karma.conf.js",
"build:ts": "tsc",
"build:dts": "npm run build:ts && api-extractor run --typescript-compiler-folder node_modules/typescript --local && rimraf 'lib/**/*.d.ts*'",
Expand Down
72 changes: 36 additions & 36 deletions src/bson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DBRef } from './db_ref';
import { Decimal128 } from './decimal128';
import { Double } from './double';
import { ensureBuffer } from './ensure_buffer';
import { EJSON } from './extended_json';
import { Int32 } from './int_32';
import { Long } from './long';
import { Map } from './map';
Expand All @@ -20,42 +21,7 @@ import { BSONSymbol } from './symbol';
import { Timestamp } from './timestamp';
export { BinaryExtended, BinaryExtendedLegacy, BinarySequence } from './binary';
export { CodeExtended } from './code';
export {
BSON_BINARY_SUBTYPE_BYTE_ARRAY,
BSON_BINARY_SUBTYPE_DEFAULT,
BSON_BINARY_SUBTYPE_FUNCTION,
BSON_BINARY_SUBTYPE_MD5,
BSON_BINARY_SUBTYPE_USER_DEFINED,
BSON_BINARY_SUBTYPE_UUID,
BSON_BINARY_SUBTYPE_UUID_NEW,
BSON_DATA_ARRAY,
BSON_DATA_BINARY,
BSON_DATA_BOOLEAN,
BSON_DATA_CODE,
BSON_DATA_CODE_W_SCOPE,
BSON_DATA_DATE,
BSON_DATA_DBPOINTER,
BSON_DATA_DECIMAL128,
BSON_DATA_INT,
BSON_DATA_LONG,
BSON_DATA_MAX_KEY,
BSON_DATA_MIN_KEY,
BSON_DATA_NULL,
BSON_DATA_NUMBER,
BSON_DATA_OBJECT,
BSON_DATA_OID,
BSON_DATA_REGEXP,
BSON_DATA_STRING,
BSON_DATA_SYMBOL,
BSON_DATA_TIMESTAMP,
BSON_DATA_UNDEFINED,
BSON_INT32_MAX,
BSON_INT32_MIN,
BSON_INT64_MAX,
BSON_INT64_MIN,
JS_INT_MAX,
JS_INT_MIN
} from './constants';
export * from './constants';
export { DBRefLike } from './db_ref';
export { Decimal128Extended } from './decimal128';
export { DoubleExtended } from './double';
Expand Down Expand Up @@ -291,3 +257,37 @@ export function deserializeStream(
// Return object containing end index of parsing and list of documents
return index;
}

/**
* BSON default export
* @deprecated Please use named exports
* @privateRemarks
* We want to someday deprecate the default export,
* so none of the new TS types are being exported on the default
* @public
*/
const BSON = {
Binary,
Code,
DBRef,
Decimal128,
Double,
Int32,
Long,
Map,
MaxKey,
MinKey,
ObjectId,
ObjectID: ObjectId,
BSONRegExp,
BSONSymbol,
Timestamp,
EJSON,
setInternalBufferSize,
serialize,
serializeWithBufferAndIndex,
deserialize,
calculateObjectSize,
deserializeStream
};
export default BSON;
3 changes: 2 additions & 1 deletion src/ensure_buffer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Buffer } from 'buffer';
import { isBuffer } from './parser/utils';

/**
* Makes sure that, if a Uint8Array is passed in, it is wrapped in a Buffer.
Expand All @@ -9,7 +10,7 @@ import { Buffer } from 'buffer';
* @throws TypeError If anything other than a Buffer or Uint8Array is passed in
*/
export function ensureBuffer(potentialBuffer: Buffer | ArrayBufferView | ArrayBuffer): Buffer {
if (Buffer.isBuffer(potentialBuffer)) {
if (isBuffer(potentialBuffer)) {
return potentialBuffer;
}

Expand Down
9 changes: 5 additions & 4 deletions src/parser/serializer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Buffer } from 'buffer';
import type { Buffer } from 'buffer';
import { Binary } from '../binary';
import type { BSONSymbol, DBRef, Document, MaxKey } from '../bson';
import type { Code } from '../code';
Expand All @@ -18,6 +18,7 @@ import type { BSONRegExp } from '../regexp';
import {
isBigInt64Array,
isBigUInt64Array,
isBuffer,
isDate,
isUint8Array,
normalizedFunctionString
Expand Down Expand Up @@ -785,7 +786,7 @@ export function serializeInto(
index = serializeNull(buffer, key, value, index, true);
} else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') {
index = serializeObjectId(buffer, key, value, index, true);
} else if (Buffer.isBuffer(value) || isUint8Array(value)) {
} else if (isBuffer(value) || isUint8Array(value)) {
index = serializeBuffer(buffer, key, value, index, true);
} else if (value instanceof RegExp || isRegExp(value)) {
index = serializeRegExp(buffer, key, value, index, true);
Expand Down Expand Up @@ -891,7 +892,7 @@ export function serializeInto(
index = serializeNull(buffer, key, value, index);
} else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') {
index = serializeObjectId(buffer, key, value, index);
} else if (Buffer.isBuffer(value) || isUint8Array(value)) {
} else if (isBuffer(value) || isUint8Array(value)) {
index = serializeBuffer(buffer, key, value, index);
} else if (value instanceof RegExp || isRegExp(value)) {
index = serializeRegExp(buffer, key, value, index);
Expand Down Expand Up @@ -997,7 +998,7 @@ export function serializeInto(
index = serializeNull(buffer, key, value, index);
} else if (value['_bsontype'] === 'ObjectId' || value['_bsontype'] === 'ObjectID') {
index = serializeObjectId(buffer, key, value, index);
} else if (Buffer.isBuffer(value) || isUint8Array(value)) {
} else if (isBuffer(value) || isUint8Array(value)) {
index = serializeBuffer(buffer, key, value, index);
} else if (value instanceof RegExp || isRegExp(value)) {
index = serializeRegExp(buffer, key, value, index);
Expand Down
2 changes: 1 addition & 1 deletion src/parser/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function haveBuffer(): boolean {

/** Callable in any environment to check if value is a Buffer */
export function isBuffer(value: unknown): value is Buffer {
return haveBuffer() && Buffer.isBuffer(value);
return typeof value === 'object' && value?.constructor?.name === 'Buffer';
}

// To ensure that 0.4 of node works correctly
Expand Down
89 changes: 89 additions & 0 deletions test/bson_older_versions_tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
'use strict';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a comment block somewhere at the top of this file would be useful to future maintainers, explaining what it is doing and why it's needed.


const newBSON = require('./register-bson');
const fs = require('fs');
const fetch = require('node-fetch').default;
const rimraf = require('rimraf');
const cp = require('child_process');

/*
* This file tests that previous versions of BSON
* serialize and deserialize correctly in the most recent version of BSON
*
* This is an unusual situation to run into as users should be using one BSON lib version
* but it does arise with sub deps etc. and we wish to protect against unexpected behavior
*
* If backwards compatibility breaks there should be clear warnings/failures
* rather than empty or zero-ed values.
*/

const OLD_VERSIONS = ['v1.1.5', 'v1.1.4'];
const getZipUrl = ver => `https://github.com/mongodb/js-bson/archive/${ver}.zip`;
const getImportPath = ver => `../bson-${ver}/js-bson-${ver.substring(1)}`;

function downloadZip(version, done) {
// downloads a zip of previous BSON version
fetch(getZipUrl(version))
.then(r => {
return r.arrayBuffer();
})
.then(r => {
fs.writeFileSync(`bson-${version}.zip`, new Uint8Array(r));
try {
// unzips the code, right now these test won't handle versions written in TS
cp.execSync(`unzip bson-${version}.zip -d bson-${version}`);
} catch (err) {
return done(err);
}
done();
});
}

describe('Current version', function () {
OLD_VERSIONS.forEach(version => {
before(function (done) {
if (Number(process.version.split('.')[0].substring(1)) < 8) {
// WHATWG fetch doesn't download correctly prior to node 8
// but we should be safe by testing on node 8 +
this.skip();
}
if (fs.existsSync(`bson-${version}.zip`)) {
fs.unlinkSync(`bson-${version}.zip`);
rimraf(`./bson-${version}`, err => {
if (err) done(err);

// download old versions
downloadZip(version, done);
});
} else {
// download old versions
downloadZip(version, done);
}
});

after(function (done) {
try {
fs.unlinkSync(`bson-${version}.zip`);
} catch (e) {
// ignore
}
rimraf(`./bson-${version}`, err => {
if (err) done(err);
done();
});
});

it(`serializes correctly against ${version} Binary class`, function () {
const oldBSON = require(getImportPath(version));
const binFromNew = {
binary: new newBSON.Binary('aaaa')
};
const binFromOld = {
binary: new oldBSON.Binary('aaaa')
};
expect(oldBSON.prototype.serialize(binFromNew).toString('hex')).to.equal(
newBSON.serialize(binFromOld).toString('hex')
);
});
});
});