diff --git a/package-lock.json b/package-lock.json index 77cdbbc8..4e4db483 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "@rollup/plugin-typescript": "^6.0.0", "@typescript-eslint/eslint-plugin": "^3.10.1", "@typescript-eslint/parser": "^3.10.1", + "array-includes": "^3.1.3", "benchmark": "^2.1.4", "chai": "^4.2.0", "downlevel-dts": "^0.7.0", @@ -39,6 +40,7 @@ "mocha": "5.2.0", "node-fetch": "^2.6.1", "nyc": "^15.1.0", + "object.entries": "^1.1.4", "prettier": "^2.1.1", "rimraf": "^3.0.2", "rollup": "^2.26.5", @@ -2491,6 +2493,25 @@ "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, + "node_modules/array-includes": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", + "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -3809,6 +3830,55 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-abstract": { + "version": "1.18.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.6.tgz", + "integrity": "sha512-kAeIT4cku5eNLNuUKhlmtuk1/TRZvQoYccn6TO0cSVdf1kzB0T7+dYuVK9MWM7l+/53W2Q8M7N2c6MQvhXFcUQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-string": "^1.0.7", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -4819,6 +4889,22 @@ "node": ">=4" } }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/git-raw-commits": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.10.tgz", @@ -5012,6 +5098,15 @@ "node": ">= 0.4.0" } }, + "node_modules/has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -5033,6 +5128,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -5226,6 +5336,20 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", @@ -5250,6 +5374,18 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -5262,6 +5398,34 @@ "node": ">=8" } }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", @@ -5274,6 +5438,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5310,6 +5489,18 @@ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", "dev": true }, + "node_modules/is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5319,6 +5510,21 @@ "node": ">=0.12.0" } }, + "node_modules/is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -5346,6 +5552,22 @@ "@types/estree": "*" } }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -5355,6 +5577,36 @@ "node": ">=8" } }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-text-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", @@ -6645,6 +6897,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -6672,6 +6933,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.4.tgz", + "integrity": "sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -7707,6 +7982,20 @@ "vscode-textmate": "5.2.0" } }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -8216,6 +8505,32 @@ "node": ">=8" } }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/stringify-package": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", @@ -8851,6 +9166,21 @@ "node": ">=0.8.0" } }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -9012,6 +9342,22 @@ "node": ">= 8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -10943,6 +11289,19 @@ "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", "dev": true }, + "array-includes": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", + "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.5" + } + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -11969,6 +12328,43 @@ "is-arrayish": "^0.2.1" } }, + "es-abstract": { + "version": "1.18.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.6.tgz", + "integrity": "sha512-kAeIT4cku5eNLNuUKhlmtuk1/TRZvQoYccn6TO0cSVdf1kzB0T7+dYuVK9MWM7l+/53W2Q8M7N2c6MQvhXFcUQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-string": "^1.0.7", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -12718,6 +13114,16 @@ "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", "dev": true }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, "git-raw-commits": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.10.tgz", @@ -12859,6 +13265,12 @@ "function-bind": "^1.1.1" } }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -12871,6 +13283,15 @@ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, "hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -13012,6 +13433,17 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, "interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", @@ -13030,6 +13462,15 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -13039,6 +13480,22 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, "is-core-module": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", @@ -13048,6 +13505,15 @@ "has": "^1.0.3" } }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -13075,12 +13541,27 @@ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", "dev": true }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -13102,12 +13583,40 @@ "@types/estree": "*" } }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, "is-text-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", @@ -14128,6 +14637,12 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, + "object-inspect": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", + "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "dev": true + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -14146,6 +14661,17 @@ "object-keys": "^1.1.1" } }, + "object.entries": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.4.tgz", + "integrity": "sha512-h4LWKWE+wKQGhtMjZEBud7uLGhqyLwj8fpHOarZhD2uY3C9cRtk57VQ89ke3moByLXMedqs3XCHzyb4AmA2DjA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.2" + } + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -14950,6 +15476,17 @@ "vscode-textmate": "5.2.0" } }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -15352,6 +15889,26 @@ "strip-ansi": "^6.0.0" } }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, "stringify-package": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", @@ -15823,6 +16380,18 @@ "dev": true, "optional": true }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -15945,6 +16514,19 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", diff --git a/package.json b/package.json index 3c772c04..94323b5b 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@rollup/plugin-typescript": "^6.0.0", "@typescript-eslint/eslint-plugin": "^3.10.1", "@typescript-eslint/parser": "^3.10.1", + "array-includes": "^3.1.3", "benchmark": "^2.1.4", "chai": "^4.2.0", "downlevel-dts": "^0.7.0", @@ -54,6 +55,7 @@ "mocha": "5.2.0", "node-fetch": "^2.6.1", "nyc": "^15.1.0", + "object.entries": "^1.1.4", "prettier": "^2.1.1", "rimraf": "^3.0.2", "rollup": "^2.26.5", diff --git a/src/binary.ts b/src/binary.ts index 8e4e10af..2b82893d 100644 --- a/src/binary.ts +++ b/src/binary.ts @@ -3,6 +3,7 @@ import { ensureBuffer } from './ensure_buffer'; import { uuidHexStringToBuffer } from './uuid_utils'; import { UUID, UUIDExtended } from './uuid'; import type { EJSONOptions } from './extended_json'; +import { BSONError, BSONTypeError } from './error'; /** @public */ export type BinarySequence = Uint8Array | Buffer | number[]; @@ -73,7 +74,7 @@ export class Binary { !(buffer instanceof ArrayBuffer) && !Array.isArray(buffer) ) { - throw new TypeError( + throw new BSONTypeError( 'Binary can only be constructed from string, Buffer, TypedArray, or Array' ); } @@ -108,9 +109,9 @@ export class Binary { put(byteValue: string | number | Uint8Array | Buffer | number[]): void { // If it's a string and a has more than one character throw an error if (typeof byteValue === 'string' && byteValue.length !== 1) { - throw new TypeError('only accepts single character String'); + throw new BSONTypeError('only accepts single character String'); } else if (typeof byteValue !== 'number' && byteValue.length !== 1) - throw new TypeError('only accepts single character Uint8Array or Array'); + throw new BSONTypeError('only accepts single character Uint8Array or Array'); // Decode the byte value once let decodedByte: number; @@ -123,7 +124,7 @@ export class Binary { } if (decodedByte < 0 || decodedByte > 255) { - throw new TypeError('only accepts number in a valid unsigned byte range 0-255'); + throw new BSONTypeError('only accepts number in a valid unsigned byte range 0-255'); } if (this.buffer.length > this.position) { @@ -238,7 +239,7 @@ export class Binary { return new UUID(this.buffer.slice(0, this.position)); } - throw new Error( + throw new BSONError( `Binary sub_type "${this.sub_type}" is not supported for converting to UUID. Only "${Binary.SUBTYPE_UUID}" is currently supported.` ); } @@ -266,7 +267,7 @@ export class Binary { data = uuidHexStringToBuffer(doc.$uuid); } if (!data) { - throw new TypeError(`Unexpected Binary Extended JSON format ${JSON.stringify(doc)}`); + throw new BSONTypeError(`Unexpected Binary Extended JSON format ${JSON.stringify(doc)}`); } return new Binary(data, type); } diff --git a/src/bson.ts b/src/bson.ts index 22acdb67..924235aa 100644 --- a/src/bson.ts +++ b/src/bson.ts @@ -12,6 +12,7 @@ import { Map } from './map'; import { MaxKey } from './max_key'; import { MinKey } from './min_key'; import { ObjectId } from './objectid'; +import { BSONError, BSONTypeError } from './error'; import { calculateObjectSize as internalCalculateObjectSize } from './parser/calculate_size'; // Parts of the parser import { deserialize as internalDeserialize, DeserializeOptions } from './parser/deserializer'; @@ -98,6 +99,7 @@ export { // later builds we changed it back to ObjectID (capital D) to match legacy implementations. ObjectId as ObjectID }; +export { BSONError, BSONTypeError } from './error'; /** @public */ export interface Document { @@ -326,6 +328,8 @@ const BSON = { serializeWithBufferAndIndex, deserialize, calculateObjectSize, - deserializeStream + deserializeStream, + BSONError, + BSONTypeError }; export default BSON; diff --git a/src/decimal128.ts b/src/decimal128.ts index a7b58317..0427dad8 100644 --- a/src/decimal128.ts +++ b/src/decimal128.ts @@ -1,4 +1,5 @@ import { Buffer } from 'buffer'; +import { BSONTypeError } from './error'; import { Long } from './long'; const PARSE_STRING_REGEXP = /^(\+|-)?(\d+|(\d*\.\d*))?(E|e)?([-+])?(\d+)?$/; @@ -105,7 +106,7 @@ function lessThan(left: Long, right: Long): boolean { } function invalidErr(string: string, message: string) { - throw new TypeError(`"${string}" is not a valid Decimal128 string - ${message}`); + throw new BSONTypeError(`"${string}" is not a valid Decimal128 string - ${message}`); } /** @public */ @@ -187,7 +188,7 @@ export class Decimal128 { // TODO: implementing a custom parsing for this, or refactoring the regex would yield // further gains. if (representation.length >= 7000) { - throw new TypeError('' + representation + ' not a valid Decimal128 string'); + throw new BSONTypeError('' + representation + ' not a valid Decimal128 string'); } // Results @@ -197,7 +198,7 @@ export class Decimal128 { // Validate the string if ((!stringMatch && !infMatch && !nanMatch) || representation.length === 0) { - throw new TypeError('' + representation + ' not a valid Decimal128 string'); + throw new BSONTypeError('' + representation + ' not a valid Decimal128 string'); } if (stringMatch) { @@ -269,7 +270,7 @@ export class Decimal128 { } if (sawRadix && !nDigitsRead) - throw new TypeError('' + representation + ' not a valid Decimal128 string'); + throw new BSONTypeError('' + representation + ' not a valid Decimal128 string'); // Read exponent if exists if (representation[index] === 'e' || representation[index] === 'E') { diff --git a/src/ensure_buffer.ts b/src/ensure_buffer.ts index 62e0dde0..d2b97832 100644 --- a/src/ensure_buffer.ts +++ b/src/ensure_buffer.ts @@ -1,4 +1,5 @@ import { Buffer } from 'buffer'; +import { BSONTypeError } from './error'; import { isAnyArrayBuffer } from './parser/utils'; /** @@ -22,5 +23,5 @@ export function ensureBuffer(potentialBuffer: Buffer | ArrayBufferView | ArrayBu return Buffer.from(potentialBuffer); } - throw new TypeError('Must use either Buffer or TypedArray'); + throw new BSONTypeError('Must use either Buffer or TypedArray'); } diff --git a/src/error.ts b/src/error.ts new file mode 100644 index 00000000..b9071d57 --- /dev/null +++ b/src/error.ts @@ -0,0 +1,13 @@ +/** @public */ +export class BSONError extends Error { + get name(): string { + return 'BSONError'; + } +} + +/** @public */ +export class BSONTypeError extends TypeError { + get name(): string { + return 'BSONTypeError'; + } +} diff --git a/src/extended_json.ts b/src/extended_json.ts index a8fe192f..e9046437 100644 --- a/src/extended_json.ts +++ b/src/extended_json.ts @@ -4,6 +4,7 @@ import { Code } from './code'; import { DBRef, isDBRefLike } from './db_ref'; import { Decimal128 } from './decimal128'; import { Double } from './double'; +import { BSONError, BSONTypeError } from './error'; import { Int32 } from './int_32'; import { Long } from './long'; import { MaxKey } from './max_key'; @@ -185,7 +186,7 @@ function serializeValue(value: any, options: EJSONSerializeOptions): any { circularPart.length + (alreadySeen.length + current.length) / 2 - 1 ); - throw new TypeError( + throw new BSONTypeError( 'Converting circular structure to EJSON:\n' + ` ${leadingPart}${alreadySeen}${circularPart}${current}\n` + ` ${leadingSpace}\\${dashes}/` @@ -274,7 +275,7 @@ const BSON_TYPE_MAPPINGS = { // eslint-disable-next-line @typescript-eslint/no-explicit-any function serializeDocument(doc: any, options: EJSONSerializeOptions) { - if (doc == null || typeof doc !== 'object') throw new Error('not an object instance'); + if (doc == null || typeof doc !== 'object') throw new BSONError('not an object instance'); const bsontype: BSONType['_bsontype'] = doc._bsontype; if (typeof bsontype === 'undefined') { @@ -300,7 +301,7 @@ function serializeDocument(doc: any, options: EJSONSerializeOptions) { // Copy the object into this library's version of that type. const mapper = BSON_TYPE_MAPPINGS[doc._bsontype]; if (!mapper) { - throw new TypeError('Unrecognized or invalid _bsontype: ' + doc._bsontype); + throw new BSONTypeError('Unrecognized or invalid _bsontype: ' + doc._bsontype); } outDoc = mapper(outDoc); } @@ -319,7 +320,7 @@ function serializeDocument(doc: any, options: EJSONSerializeOptions) { return outDoc.toExtendedJSON(options); } else { - throw new Error('_bsontype must be a string, but was: ' + typeof bsontype); + throw new BSONError('_bsontype must be a string, but was: ' + typeof bsontype); } } @@ -366,7 +367,14 @@ export namespace EJSON { if (typeof finalOptions.relaxed === 'boolean') finalOptions.strict = !finalOptions.relaxed; if (typeof finalOptions.strict === 'boolean') finalOptions.relaxed = !finalOptions.strict; - return JSON.parse(text, (_key, value) => deserializeValue(value, finalOptions)); + return JSON.parse(text, (key, value) => { + if (key.indexOf('\x00') !== -1) { + throw new BSONError( + `BSON Document field names cannot contain null bytes, found: ${JSON.stringify(key)}` + ); + } + return deserializeValue(value, finalOptions); + }); } export type JSONPrimitive = string | number | boolean | null; diff --git a/src/objectid.ts b/src/objectid.ts index aab7fcb5..a13165e9 100644 --- a/src/objectid.ts +++ b/src/objectid.ts @@ -1,5 +1,6 @@ import { Buffer } from 'buffer'; import { ensureBuffer } from './ensure_buffer'; +import { BSONTypeError } from './error'; import { deprecate, isUint8Array, randomBytes } from './parser/utils'; // Regular expression that checks for hex value @@ -84,7 +85,7 @@ export class ObjectId { } else if (id.length === 24 && checkForHexRegExp.test(id)) { this[kId] = Buffer.from(id, 'hex'); } else { - throw new TypeError( + throw new BSONTypeError( 'Argument passed in must be a Buffer or string of 12 bytes or a string of 24 hex characters' ); } @@ -276,7 +277,7 @@ export class ObjectId { static createFromHexString(hexString: string): ObjectId { // Throw an error if it's not a valid setup if (typeof hexString === 'undefined' || (hexString != null && hexString.length !== 24)) { - throw new TypeError( + throw new BSONTypeError( 'Argument passed in must be a single String of 12 bytes or a string of 24 hex characters' ); } diff --git a/src/parser/deserializer.ts b/src/parser/deserializer.ts index 3806c0b1..3ba05499 100644 --- a/src/parser/deserializer.ts +++ b/src/parser/deserializer.ts @@ -6,6 +6,7 @@ import * as constants from '../constants'; import { DBRef, DBRefLike, isDBRefLike } from '../db_ref'; import { Decimal128 } from '../decimal128'; import { Double } from '../double'; +import { BSONError } from '../error'; import { Int32 } from '../int_32'; import { Long } from '../long'; import { MaxKey } from '../max_key'; @@ -67,26 +68,28 @@ export function deserialize( (buffer[index + 3] << 24); if (size < 5) { - throw new Error(`bson size must be >= 5, is ${size}`); + throw new BSONError(`bson size must be >= 5, is ${size}`); } if (options.allowObjectSmallerThanBufferSize && buffer.length < size) { - throw new Error(`buffer length ${buffer.length} must be >= bson size ${size}`); + throw new BSONError(`buffer length ${buffer.length} must be >= bson size ${size}`); } if (!options.allowObjectSmallerThanBufferSize && buffer.length !== size) { - throw new Error(`buffer length ${buffer.length} must === bson size ${size}`); + throw new BSONError(`buffer length ${buffer.length} must === bson size ${size}`); } if (size + index > buffer.byteLength) { - throw new Error( + throw new BSONError( `(bson size ${size} + options.index ${index} must be <= buffer length ${buffer.byteLength})` ); } // Illegal end value if (buffer[index + size - 1] !== 0) { - throw new Error("One object, sized correctly, with a spot for an EOO, but the EOO isn't 0x00"); + throw new BSONError( + "One object, sized correctly, with a spot for an EOO, but the EOO isn't 0x00" + ); } // Start deserializtion @@ -121,14 +124,14 @@ function deserializeObject( const startIndex = index; // Validate that we have at least 4 bytes of buffer - if (buffer.length < 5) throw new Error('corrupt bson message < 5 bytes long'); + if (buffer.length < 5) throw new BSONError('corrupt bson message < 5 bytes long'); // Read the document size const size = buffer[index++] | (buffer[index++] << 8) | (buffer[index++] << 16) | (buffer[index++] << 24); // Ensure buffer is valid size - if (size < 5 || size > buffer.length) throw new Error('corrupt bson message'); + if (size < 5 || size > buffer.length) throw new BSONError('corrupt bson message'); // Create holding object const object: Document = isArray ? [] : {}; @@ -154,7 +157,7 @@ function deserializeObject( } // If are at the end of the buffer there is a problem with the document - if (i >= buffer.byteLength) throw new Error('Bad BSON Document: illegal CString'); + if (i >= buffer.byteLength) throw new BSONError('Bad BSON Document: illegal CString'); const name = isArray ? arrayIndex++ : buffer.toString('utf8', index, i); if (isPossibleDBRef !== false && (name as string)[0] === '$') { isPossibleDBRef = allowedDBRefKeys.test(name as string); @@ -174,7 +177,7 @@ function deserializeObject( stringSize > buffer.length - index || buffer[index + stringSize - 1] !== 0 ) { - throw new Error('bad string length in bson'); + throw new BSONError('bad string length in bson'); } value = getValidatedString(buffer, index, index + stringSize - 1); @@ -214,7 +217,8 @@ function deserializeObject( (buffer[index++] << 24); value = new Date(new Long(lowBits, highBits).toNumber()); } else if (elementType === constants.BSON_DATA_BOOLEAN) { - if (buffer[index] !== 0 && buffer[index] !== 1) throw new Error('illegal boolean type value'); + if (buffer[index] !== 0 && buffer[index] !== 1) + throw new BSONError('illegal boolean type value'); value = buffer[index++] === 1; } else if (elementType === constants.BSON_DATA_OBJECT) { const _index = index; @@ -224,7 +228,7 @@ function deserializeObject( (buffer[index + 2] << 16) | (buffer[index + 3] << 24); if (objectSize <= 0 || objectSize > buffer.length - index) - throw new Error('bad embedded document length in bson'); + throw new BSONError('bad embedded document length in bson'); // We have a raw value if (raw) { @@ -262,8 +266,8 @@ function deserializeObject( value = deserializeObject(buffer, _index, arrayOptions, true); index = index + objectSize; - if (buffer[index - 1] !== 0) throw new Error('invalid array terminator byte'); - if (index !== stopIndex) throw new Error('corrupted array bson'); + if (buffer[index - 1] !== 0) throw new BSONError('invalid array terminator byte'); + if (index !== stopIndex) throw new BSONError('corrupted array bson'); } else if (elementType === constants.BSON_DATA_UNDEFINED) { value = undefined; } else if (elementType === constants.BSON_DATA_NULL) { @@ -315,11 +319,11 @@ function deserializeObject( const subType = buffer[index++]; // Did we have a negative binary size, throw - if (binarySize < 0) throw new Error('Negative binary type element size found'); + if (binarySize < 0) throw new BSONError('Negative binary type element size found'); // Is the length longer than the document if (binarySize > buffer.byteLength) - throw new Error('Binary type size larger than document size'); + throw new BSONError('Binary type size larger than document size'); // Decode as raw Buffer object if options specifies it if (buffer['slice'] != null) { @@ -331,11 +335,11 @@ function deserializeObject( (buffer[index++] << 16) | (buffer[index++] << 24); if (binarySize < 0) - throw new Error('Negative binary type element size found for subtype 0x02'); + throw new BSONError('Negative binary type element size found for subtype 0x02'); if (binarySize > totalBinarySize - 4) - throw new Error('Binary type with subtype 0x02 contains too long binary size'); + throw new BSONError('Binary type with subtype 0x02 contains too long binary size'); if (binarySize < totalBinarySize - 4) - throw new Error('Binary type with subtype 0x02 contains too short binary size'); + throw new BSONError('Binary type with subtype 0x02 contains too short binary size'); } if (promoteBuffers && promoteValues) { @@ -353,11 +357,11 @@ function deserializeObject( (buffer[index++] << 16) | (buffer[index++] << 24); if (binarySize < 0) - throw new Error('Negative binary type element size found for subtype 0x02'); + throw new BSONError('Negative binary type element size found for subtype 0x02'); if (binarySize > totalBinarySize - 4) - throw new Error('Binary type with subtype 0x02 contains too long binary size'); + throw new BSONError('Binary type with subtype 0x02 contains too long binary size'); if (binarySize < totalBinarySize - 4) - throw new Error('Binary type with subtype 0x02 contains too short binary size'); + throw new BSONError('Binary type with subtype 0x02 contains too short binary size'); } // Copy the data @@ -382,7 +386,7 @@ function deserializeObject( i++; } // If are at the end of the buffer there is a problem with the document - if (i >= buffer.length) throw new Error('Bad BSON Document: illegal CString'); + if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString'); // Return the C string const source = buffer.toString('utf8', index, i); // Create the regexp @@ -395,7 +399,7 @@ function deserializeObject( i++; } // If are at the end of the buffer there is a problem with the document - if (i >= buffer.length) throw new Error('Bad BSON Document: illegal CString'); + if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString'); // Return the C string const regExpOptions = buffer.toString('utf8', index, i); index = i + 1; @@ -427,7 +431,7 @@ function deserializeObject( i++; } // If are at the end of the buffer there is a problem with the document - if (i >= buffer.length) throw new Error('Bad BSON Document: illegal CString'); + if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString'); // Return the C string const source = buffer.toString('utf8', index, i); index = i + 1; @@ -439,7 +443,7 @@ function deserializeObject( i++; } // If are at the end of the buffer there is a problem with the document - if (i >= buffer.length) throw new Error('Bad BSON Document: illegal CString'); + if (i >= buffer.length) throw new BSONError('Bad BSON Document: illegal CString'); // Return the C string const regExpOptions = buffer.toString('utf8', index, i); index = i + 1; @@ -457,7 +461,7 @@ function deserializeObject( stringSize > buffer.length - index || buffer[index + stringSize - 1] !== 0 ) { - throw new Error('bad string length in bson'); + throw new BSONError('bad string length in bson'); } const symbol = getValidatedString(buffer, index, index + stringSize - 1); value = promoteValues ? symbol : new BSONSymbol(symbol); @@ -490,7 +494,7 @@ function deserializeObject( stringSize > buffer.length - index || buffer[index + stringSize - 1] !== 0 ) { - throw new Error('bad string length in bson'); + throw new BSONError('bad string length in bson'); } const functionString = getValidatedString(buffer, index, index + stringSize - 1); @@ -518,7 +522,7 @@ function deserializeObject( // Element cannot be shorter than totalSize + stringSize + documentSize + terminator if (totalSize < 4 + 4 + 4 + 1) { - throw new Error('code_w_scope total size shorter minimum expected length'); + throw new BSONError('code_w_scope total size shorter minimum expected length'); } // Get the code string size @@ -533,7 +537,7 @@ function deserializeObject( stringSize > buffer.length - index || buffer[index + stringSize - 1] !== 0 ) { - throw new Error('bad string length in bson'); + throw new BSONError('bad string length in bson'); } // Javascript function @@ -555,12 +559,12 @@ function deserializeObject( // Check if field length is too short if (totalSize < 4 + 4 + objectSize + stringSize) { - throw new Error('code_w_scope total size is too short, truncating scope'); + throw new BSONError('code_w_scope total size is too short, truncating scope'); } // Check if totalSize field is too long if (totalSize > 4 + 4 + objectSize + stringSize) { - throw new Error('code_w_scope total size is too long, clips outer document'); + throw new BSONError('code_w_scope total size is too long, clips outer document'); } // If we are evaluating the functions @@ -590,10 +594,10 @@ function deserializeObject( stringSize > buffer.length - index || buffer[index + stringSize - 1] !== 0 ) - throw new Error('bad string length in bson'); + throw new BSONError('bad string length in bson'); // Namespace if (!validateUtf8(buffer, index, index + stringSize - 1)) { - throw new Error('Invalid UTF-8 string in BSON document'); + throw new BSONError('Invalid UTF-8 string in BSON document'); } const namespace = buffer.toString('utf8', index, index + stringSize - 1); // Update parse index position @@ -610,7 +614,7 @@ function deserializeObject( // Upgrade to DBRef type value = new DBRef(namespace, oid); } else { - throw new Error( + throw new BSONError( 'Detected unknown BSON type ' + elementType.toString(16) + ' for fieldname "' + name + '"' ); } @@ -628,8 +632,8 @@ function deserializeObject( // Check if the deserialization was against a valid array/object if (size !== index - startIndex) { - if (isArray) throw new Error('corrupt array bson'); - throw new Error('corrupt object bson'); + if (isArray) throw new BSONError('corrupt array bson'); + throw new BSONError('corrupt object bson'); } // if we did not find "$ref", "$id", "$db", or found an extraneous $key, don't make a DBRef @@ -671,7 +675,7 @@ function getValidatedString(buffer: Buffer, start: number, end: number) { for (let i = 0; i < value.length; i++) { if (value.charCodeAt(i) === 0xfffd) { if (!validateUtf8(buffer, start, end)) { - throw new Error('Invalid UTF-8 string in BSON document'); + throw new BSONError('Invalid UTF-8 string in BSON document'); } break; } diff --git a/src/parser/serializer.ts b/src/parser/serializer.ts index 5dd58746..db9e015a 100644 --- a/src/parser/serializer.ts +++ b/src/parser/serializer.ts @@ -7,6 +7,7 @@ import type { DBRefLike } from '../db_ref'; import type { Decimal128 } from '../decimal128'; import type { Double } from '../double'; import { ensureBuffer } from '../ensure_buffer'; +import { BSONError, BSONTypeError } from '../error'; import { isBSONType } from '../extended_json'; import { writeIEEE754 } from '../float_parser'; import type { Int32 } from '../int_32'; @@ -311,7 +312,7 @@ function serializeObjectId( // browser polyfill buffer.set(value.id.subarray(0, 12), index); } else { - throw new TypeError('object [' + JSON.stringify(value) + '] is not a valid ObjectId'); + throw new BSONTypeError('object [' + JSON.stringify(value) + '] is not a valid ObjectId'); } // Adjust index @@ -363,7 +364,7 @@ function serializeObject( path: Document[] = [] ) { for (let i = 0; i < path.length; i++) { - if (path[i] === value) throw new Error('cyclic dependency detected'); + if (path[i] === value) throw new BSONError('cyclic dependency detected'); } // Push value to stack @@ -767,7 +768,7 @@ export function serializeInto( // Is there an override value if (value && value.toBSON) { - if (typeof value.toBSON !== 'function') throw new TypeError('toBSON is not a function'); + if (typeof value.toBSON !== 'function') throw new BSONTypeError('toBSON is not a function'); value = value.toBSON(); } @@ -776,7 +777,7 @@ export function serializeInto( } else if (typeof value === 'number') { index = serializeNumber(buffer, key, value, index, true); } else if (typeof value === 'bigint') { - throw new TypeError('Unsupported type BigInt, please use Decimal128'); + throw new BSONTypeError('Unsupported type BigInt, please use Decimal128'); } else if (typeof value === 'boolean') { index = serializeBoolean(buffer, key, value, index, true); } else if (value instanceof Date || isDate(value)) { @@ -841,7 +842,7 @@ export function serializeInto( } else if (value['_bsontype'] === 'MinKey' || value['_bsontype'] === 'MaxKey') { index = serializeMinMax(buffer, key, value, index, true); } else if (typeof value['_bsontype'] !== 'undefined') { - throw new TypeError('Unrecognized or invalid _bsontype: ' + value['_bsontype']); + throw new BSONTypeError('Unrecognized or invalid _bsontype: ' + value['_bsontype']); } } } else if (object instanceof Map || isMap(object)) { @@ -884,7 +885,7 @@ export function serializeInto( } else if (type === 'number') { index = serializeNumber(buffer, key, value, index); } else if (type === 'bigint' || isBigInt64Array(value) || isBigUInt64Array(value)) { - throw new TypeError('Unsupported type BigInt, please use Decimal128'); + throw new BSONTypeError('Unsupported type BigInt, please use Decimal128'); } else if (type === 'boolean') { index = serializeBoolean(buffer, key, value, index); } else if (value instanceof Date || isDate(value)) { @@ -942,16 +943,16 @@ export function serializeInto( } else if (value['_bsontype'] === 'MinKey' || value['_bsontype'] === 'MaxKey') { index = serializeMinMax(buffer, key, value, index); } else if (typeof value['_bsontype'] !== 'undefined') { - throw new TypeError('Unrecognized or invalid _bsontype: ' + value['_bsontype']); + throw new BSONTypeError('Unrecognized or invalid _bsontype: ' + value['_bsontype']); } } } else { // Did we provide a custom serialization method if (object.toBSON) { - if (typeof object.toBSON !== 'function') throw new TypeError('toBSON is not a function'); + if (typeof object.toBSON !== 'function') throw new BSONTypeError('toBSON is not a function'); object = object.toBSON(); if (object != null && typeof object !== 'object') - throw new TypeError('toBSON function did not return an object'); + throw new BSONTypeError('toBSON function did not return an object'); } // Iterate over all the keys @@ -959,7 +960,7 @@ export function serializeInto( let value = object[key]; // Is there an override value if (value && value.toBSON) { - if (typeof value.toBSON !== 'function') throw new TypeError('toBSON is not a function'); + if (typeof value.toBSON !== 'function') throw new BSONTypeError('toBSON is not a function'); value = value.toBSON(); } @@ -988,7 +989,7 @@ export function serializeInto( } else if (type === 'number') { index = serializeNumber(buffer, key, value, index); } else if (type === 'bigint') { - throw new TypeError('Unsupported type BigInt, please use Decimal128'); + throw new BSONTypeError('Unsupported type BigInt, please use Decimal128'); } else if (type === 'boolean') { index = serializeBoolean(buffer, key, value, index); } else if (value instanceof Date || isDate(value)) { @@ -1048,7 +1049,7 @@ export function serializeInto( } else if (value['_bsontype'] === 'MinKey' || value['_bsontype'] === 'MaxKey') { index = serializeMinMax(buffer, key, value, index); } else if (typeof value['_bsontype'] !== 'undefined') { - throw new TypeError('Unrecognized or invalid _bsontype: ' + value['_bsontype']); + throw new BSONTypeError('Unrecognized or invalid _bsontype: ' + value['_bsontype']); } } } diff --git a/src/regexp.ts b/src/regexp.ts index 667d3d5a..7151bec5 100644 --- a/src/regexp.ts +++ b/src/regexp.ts @@ -1,3 +1,4 @@ +import { BSONError, BSONTypeError } from './error'; import type { EJSONOptions } from './extended_json'; function alphabetize(str: string): string { @@ -37,6 +38,17 @@ export class BSONRegExp { this.pattern = pattern; this.options = alphabetize(options ?? ''); + if (this.pattern.indexOf('\x00') !== -1) { + throw new BSONError( + `BSON Regex patterns cannot contain null bytes, found: ${JSON.stringify(this.pattern)}` + ); + } + if (this.options.indexOf('\x00') !== -1) { + throw new BSONError( + `BSON Regex options cannot contain null bytes, found: ${JSON.stringify(this.options)}` + ); + } + // Validate options for (let i = 0; i < this.options.length; i++) { if ( @@ -49,7 +61,7 @@ export class BSONRegExp { this.options[i] === 'u' ) ) { - throw new Error(`The regular expression option [${this.options[i]}] is not supported`); + throw new BSONError(`The regular expression option [${this.options[i]}] is not supported`); } } } @@ -85,7 +97,7 @@ export class BSONRegExp { BSONRegExp.parseOptions(doc.$regularExpression.options) ); } - throw new TypeError(`Unexpected BSONRegExp EJSON object form: ${JSON.stringify(doc)}`); + throw new BSONTypeError(`Unexpected BSONRegExp EJSON object form: ${JSON.stringify(doc)}`); } } diff --git a/src/uuid.ts b/src/uuid.ts index 38d12d66..596baf3d 100644 --- a/src/uuid.ts +++ b/src/uuid.ts @@ -3,6 +3,7 @@ import { ensureBuffer } from './ensure_buffer'; import { Binary } from './binary'; import { bufferToUuidHexString, uuidHexStringToBuffer, uuidValidateString } from './uuid_utils'; import { isUint8Array, randomBytes } from './parser/utils'; +import { BSONTypeError } from './error'; /** @public */ export type UUIDExtended = { @@ -45,7 +46,7 @@ export class UUID { } else if (typeof input === 'string') { this.id = uuidHexStringToBuffer(input); } else { - throw new TypeError( + throw new BSONTypeError( 'Argument passed in UUID constructor must be a UUID, a 16 byte Buffer or a 32/36 character hex string (dashes excluded/included, format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).' ); } diff --git a/src/uuid_utils.ts b/src/uuid_utils.ts index edae0da5..f37b0659 100644 --- a/src/uuid_utils.ts +++ b/src/uuid_utils.ts @@ -1,4 +1,5 @@ import { Buffer } from 'buffer'; +import { BSONTypeError } from './error'; // Validation regex for v4 uuid (validates with or without dashes) const VALIDATION_REGEX = @@ -9,7 +10,7 @@ export const uuidValidateString = (str: string): boolean => export const uuidHexStringToBuffer = (hexString: string): Buffer => { if (!uuidValidateString(hexString)) { - throw new TypeError( + throw new BSONTypeError( 'UUID string representations must be a 32 or 36 character hex string (dashes excluded/included). Format: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" or "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx".' ); } diff --git a/test/node/bson_corpus_tests.js b/test/node/bson_corpus_tests.js index d6be7fa3..5973b5af 100644 --- a/test/node/bson_corpus_tests.js +++ b/test/node/bson_corpus_tests.js @@ -1,3 +1,4 @@ +//@ts-check 'use strict'; const Buffer = require('buffer').Buffer; @@ -48,12 +49,69 @@ function normalize(cEJ) { return JSON.stringify(JSON.parse(cEJ)); } -// tests from the corpus that we need to skip, and explanations why +const parseErrorForDecimal128 = scenario => { + // TODO(NODE-3637): remove regex of skipped tests and and add errors to d128 parsing + const skipRegex = /dqbsr|Inexact/; + for (const parseError of scenario.parseErrors) { + it(parseError.description, function () { + if (skipRegex.test(parseError.description)) { + this.skip(); + } + + expect( + () => BSON.Decimal128.fromString(parseError.string), + `Decimal.fromString('${parseError.string}') should throw` + ).to.throw(/not a valid Decimal128 string/); + }); + } +}; + +const parseErrorForBinary = scenario => { + for (const parseError of scenario.parseErrors) { + it(parseError.description, () => { + // Currently the BSON Binary parseError tests only check parsing relating to UUID + // in the future this regex may need expansion + expect(() => EJSON.parse(parseError.string)).to.throw(/UUID/); + }); + } +}; + +const parseErrorForRootDocument = scenario => { + for (const parseError of scenario.parseErrors) { + it(parseError.description, function () { + let caughtError; + try { + // Make sure not add anything more than this line to the try block + // Assertions, for example, throw and will mess up the checks below. + EJSON.parse(parseError.string); + } catch (error) { + caughtError = error; + } + + if (/Null/.test(parseError.description)) { + expect(caughtError).to.be.instanceOf(Error); + expect(caughtError.message).to.match(/null bytes/); + } else if (/Bad/.test(parseError.description)) { + // There is a number of failing tests that start with 'Bad' + // so this check is essentially making the test optional for now + // This should assert that e is an Error and something about the message + // TODO(NODE-3637): remove special logic and use expect().to.throw() and add errors to lib + expect(caughtError).to.satisfy(e => { + if (e instanceof Error) return true; + else this.skip(); + }); + } else { + expect(caughtError).to.be.instanceOf(Error); + } + }); + } +}; + const skipBSON = { 'NaN with payload': 'passing this would require building a custom type to store the NaN payload data.' }; - +// tests from the corpus that we need to skip, and explanations why const skipExtendedJSON = { 'Timestamp with high-order bit set on both seconds and increment': 'Current BSON implementation of timestamp/long cannot hold these values - 1 too large.', @@ -61,9 +119,18 @@ const skipExtendedJSON = { 'Current BSON implementation of timestamp/long cannot hold these values - 1 too large.' }; +const SKIP_TESTS = new Map([ + ...Object.entries(skipBSON), + ...Object.entries(skipExtendedJSON), + [ + 'All BSON types', + 'there is just too much variation in the specified expectation to make this work' + ] +]); + const corpus = require('./tools/bson_corpus_test_loader'); describe('BSON Corpus', function () { - corpus.forEach(scenario => { + for (const scenario of corpus) { const deprecated = scenario.deprecated; const description = scenario.description; const valid = scenario.valid || []; @@ -71,17 +138,10 @@ describe('BSON Corpus', function () { describe(description, function () { if (valid) { describe('valid-bson', function () { - valid.forEach(v => { - if (Reflect.has(skipBSON, v.description)) { - it.skip(v.description, () => {}); - return; - } - + for (const v of valid) { it(v.description, function () { - if (v.description === 'All BSON types' && deprecated) { - // there is just too much variation in the specified expectation to make this work + if (SKIP_TESTS.has(v.description)) { this.skip(); - return; } const cB = Buffer.from(v.canonical_bson, 'hex'); @@ -97,11 +157,8 @@ describe('BSON Corpus', function () { const convB = Buffer.from(v.converted_bson, 'hex'); expect(convB).to.deep.equal(roundTripped); } else { - const roundTripped = BSON.serialize( - BSON.deserialize(cB, deserializeOptions), - serializeOptions - ); - + const jsObject = BSON.deserialize(cB, deserializeOptions); + const roundTripped = BSON.serialize(jsObject, serializeOptions); expect(cB).to.deep.equal(roundTripped); } @@ -117,17 +174,15 @@ describe('BSON Corpus', function () { ); } }); - }); + } }); describe('valid-extjson', function () { - valid.forEach(v => { - if (Reflect.has(skipExtendedJSON, v.description)) { - it.skip(v.description, () => {}); - return; - } - + for (const v of valid) { it(v.description, function () { + if (SKIP_TESTS.has(v.description)) { + this.skip(); + } // read in test case data. if this scenario is for a deprecated // type, we want to use the "converted" BSON and EJSON, which // use the upgraded version of the deprecated type. otherwise, @@ -177,30 +232,34 @@ describe('BSON Corpus', function () { expect(nativeToREJSON(jsonToNative(rEJ))).to.equal(rEJ); } }); - }); + } }); } if (scenario.decodeErrors) { describe('decodeErrors', function () { - scenario.decodeErrors.forEach(d => { + for (const d of scenario.decodeErrors) { it(d.description, function () { const B = Buffer.from(d.bson, 'hex'); expect(() => BSON.deserialize(B, deserializeOptions)).to.throw(); }); - }); + } }); } if (scenario.parseErrors) { describe('parseErrors', function () { - scenario.parseErrors.forEach(p => { - it(p.description, function () { - expect(() => jsonToNative(scenario.string)).to.throw(); - }); - }); + if (description === 'Decimal128') { + parseErrorForDecimal128(scenario); + } else if (description === 'Binary type') { + parseErrorForBinary(scenario); + } else if (description === 'Top-level document validity') { + parseErrorForRootDocument(scenario); + } else { + expect.fail(`No parseError implementation for '${description}''`); + } }); } }); - }); + } }); diff --git a/test/node/bson_test.js b/test/node/bson_test.js index c5d11ae5..c37461ab 100644 --- a/test/node/bson_test.js +++ b/test/node/bson_test.js @@ -2016,9 +2016,7 @@ describe('BSON', function () { }); it('should throw when null byte in Flags/options for a regular expression', () => { - expect(() => BSON.serialize({ a: new BSONRegExp('a', 'i\x00m') })).to.throw( - /regular expression option/ - ); + expect(() => BSON.serialize({ a: new BSONRegExp('a', 'i\x00m') })).to.throw(/null bytes/); }); }); }); diff --git a/test/node/test_full_bson.js b/test/node/test_full_bson.js index 7f9fe172..2e1e46b5 100644 --- a/test/node/test_full_bson.js +++ b/test/node/test_full_bson.js @@ -345,9 +345,9 @@ describe('Full BSON', function () { it('Should correctly fail to serialize BSONRegExp with null bytes', function (done) { var doc = {}; - doc.test = new BSONRegExp('a\0b'); try { + doc.test = new BSONRegExp('a\0b'); BSON.serialize(doc, { checkKeys: true }); diff --git a/test/node/tools/bson_corpus_test_loader.js b/test/node/tools/bson_corpus_test_loader.js index 4d3592fd..0bee39c9 100644 --- a/test/node/tools/bson_corpus_test_loader.js +++ b/test/node/tools/bson_corpus_test_loader.js @@ -3,6 +3,45 @@ const path = require('path'); const fs = require('fs'); +/** + * @typedef {object} DecodeErrorTest + * @property {string} description + * @property {string} bson + */ + +/** + * @typedef {object} ParseErrorTest + * @property {string} description + * @property {string} string + */ + +/** + * @typedef {object} ValidTest + * @property {string} description + * @property {string} canonical_bson + * @property {string} [canonical_extjson] + * @property {string} [degenerate_extjson] + * @property {string} [converted_bson] + * @property {string} [converted_extjson] + * @property {string} [degenerate_bson] + * @property {boolean} [lossy] + * @property {string} [relaxed_extjson] + */ + +/** + * @typedef {object} BSONCorpus + * @property {string} description + * @property {string} bson_type + * @property {string} test_key + * @property {ValidTest[]} valid + * @property {DecodeErrorTest[]} [decodeErrors] + * @property {ParseErrorTest[]} [parseErrors] + * @property {boolean} [deprecated] + */ + +/** + * @returns {BSONCorpus[]} + */ function findScenarios() { return fs .readdirSync(path.join(__dirname, '../specs/bson-corpus')) diff --git a/test/register-bson.js b/test/register-bson.js index 7a2fcd0a..4061d2a3 100644 --- a/test/register-bson.js +++ b/test/register-bson.js @@ -8,10 +8,31 @@ // This should be done by mocha --require, but that isn't supported until mocha version 7+ require('chai/register-expect'); +require('array-includes/auto'); +require('object.entries/auto'); const BSON = require('../lib/bson'); const { ensureBuffer } = require('../lib/ensure_buffer'); - BSON.ensureBuffer = ensureBuffer; +const { Assertion, util } = require('chai'); +Assertion.overwriteMethod('throw', function (original) { + return function assertThrow(...args) { + if (args.length === 0 || args.includes(BSON.BSONError) || args.includes(BSON.BSONTypeError)) { + // By default, lets check for BSONError or BSONTypeError + // Since we compile to es5 instanceof is broken??? + const assertion = original.apply(this, args); + const object = util.flag(assertion, 'object'); + return this.assert( + object && /BSONError|BSONTypeError/.test(object.stack), + 'expected #{this} to be a BSONError or a BSONTypeError but got #{act}', + 'expected #{this} to not be a BSONError nor a BSONTypeError but got #{act}', + (object && object.stack) || '' + ); + } else { + return original.apply(this, args); + } + }; +}); + module.exports = BSON;