diff --git a/package-lock.json b/package-lock.json index 386ea0d..2142c57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,12 @@ { "name": "o.js", - "version": "1.3.0", + "version": "1.3.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "1.3.0", + "name": "o.js", + "version": "1.3.1", "license": "MIT", "dependencies": { "cross-fetch": "^3.0.6", @@ -2679,8 +2680,7 @@ "esprima": "^4.0.1", "estraverse": "^4.2.0", "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" + "optionator": "^0.8.1" }, "bin": { "escodegen": "bin/escodegen.js", @@ -3776,9 +3776,6 @@ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, "engines": { "node": ">=8" }, @@ -4558,7 +4555,6 @@ "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", "graceful-fs": "^4.2.4", "jest-regex-util": "^26.0.0", "jest-serializer": "^26.5.0", @@ -5870,9 +5866,6 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, - "dependencies": { - "graceful-fs": "^4.1.6" - }, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -7096,8 +7089,7 @@ "dev": true, "dependencies": { "commander": "~2.9.0", - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0" + "source-map": "~0.5.1" }, "bin": { "uglifyjs": "bin/uglifyjs" @@ -8062,9 +8054,9 @@ } }, "node_modules/tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/tslint": { "version": "5.12.1", @@ -15337,9 +15329,9 @@ } }, "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "tslint": { "version": "5.12.1", diff --git a/src/ORequest.spec.ts b/src/ORequest.spec.ts new file mode 100644 index 0000000..b3b9b33 --- /dev/null +++ b/src/ORequest.spec.ts @@ -0,0 +1,20 @@ +import { ORequest } from "./ORequest"; + +describe("applyQuery", () => { + it('does not use "application/x-www-form-urlencoded" encoding', () => { + expect(new ORequest(new URL("https://example.com"), {}).applyQuery({ + $top: 4, + $filter: "foo eq bar or startsWith(foo, 'baz')", + }).url.href) + // Space is encoded as "%20" and not "+" + .toBe("https://example.com/?%24top=4&%24filter=foo%20eq%20bar%20or%20startsWith%28foo%2C%20%27baz%27%29"); + }); + + it("considers existing query parameters, overwrites with entries from queries", () => { + expect(new ORequest(new URL("https://example.com?$top=5&$orderby=Some+Field&$search=some%20term"), {}).applyQuery({ + $top: 4, + $filter: "foo eq bar or startsWith(foo, 'baz')", + }).url.href) + .toBe("https://example.com/?%24top=4&%24filter=foo%20eq%20bar%20or%20startsWith%28foo%2C%20%27baz%27%29&%24orderby=Some%20Field&%24search=some%20term"); + }); +}); diff --git a/src/ORequest.ts b/src/ORequest.ts index b96624a..73baddb 100644 --- a/src/ORequest.ts +++ b/src/ORequest.ts @@ -1,5 +1,9 @@ import { OdataQuery } from "./OdataQuery"; +const encodeURIComponentStrict = + (str: string) => encodeURIComponent(str) + .replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`); + export class ORequest { public url: URL; @@ -16,15 +20,16 @@ export class ORequest { return fetch(req, this.config); } - public applyQuery(query?: OdataQuery) { - for (const key in query) { - if (query.hasOwnProperty(key)) { - if (this.url.searchParams.get(key)) { - this.url.searchParams.set(key, query[key]); - } else { - this.url.searchParams.append(key, query[key]); - } + public applyQuery(query: OdataQuery = {}) { + this.url.searchParams.forEach((value, key) => { + if (!query.hasOwnProperty(key)) { + query[key] = value; } - } + }); + + this.url.search = Object.entries(query) + .map(([key, value]) => `${encodeURIComponentStrict(key)}=${encodeURIComponentStrict(value)}`) + .join("&"); + return this; } } diff --git a/src/o.spec.ts b/src/o.spec.ts index 1101f8c..4aa542a 100644 --- a/src/o.spec.ts +++ b/src/o.spec.ts @@ -130,7 +130,7 @@ describe("Instant request", () => { // expect expect(decodeURIComponent((data as Response).url)).toContain( - "People?$top=1&$filter=FirstName+eq+'john'" + "People?$top=1&$filter=FirstName eq 'john'" ); }); @@ -247,7 +247,7 @@ describe("Request handling", () => { // expect expect(decodeURIComponent(req[0].url)).toContain( - "People?$top=1&$filter=FirstName+eq+'john'" + "People?$top=1&$filter=FirstName eq 'john'" ); });