From daf39eb5449246a4ce557d39523011afa3c616fd Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Mon, 8 Apr 2024 10:05:14 +0200 Subject: [PATCH 1/3] fix(execute): rework encodeDisallowedCharacters function --- src/execute/oas3/parameter-builders.js | 4 +-- src/execute/oas3/style-serializer.js | 34 +++++++++----------------- test/oas3/execute/style-serializer.js | 10 ++++---- 3 files changed, 18 insertions(+), 30 deletions(-) diff --git a/src/execute/oas3/parameter-builders.js b/src/execute/oas3/parameter-builders.js index 1d0415ab1..8bf8c7900 100644 --- a/src/execute/oas3/parameter-builders.js +++ b/src/execute/oas3/parameter-builders.js @@ -1,4 +1,4 @@ -import stylize, { encodeDisallowedCharacters } from './style-serializer.js'; +import stylize, { encodeCharacters } from './style-serializer.js'; import serialize from './content-serializer.js'; export function path({ req, value, parameter }) { @@ -11,7 +11,7 @@ export function path({ req, value, parameter }) { req.url = req.url .split(`{${name}}`) - .join(encodeDisallowedCharacters(serialize(value, effectiveMediaType), { escape: true })); + .join(encodeCharacters(serialize(value, effectiveMediaType))); } else { const styledValue = stylize({ key: parameter.name, diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index 636043284..099d71312 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -1,35 +1,19 @@ const isRfc3986Reserved = (char) => ":/?#[]@!$&'()*+,;=".indexOf(char) > -1; -const isRrc3986Unreserved = (char) => /^[a-z0-9\-._~]+$/i.test(char); +const isRfc3986Unreserved = (char) => /^[a-z0-9\-._~]+$/i.test(char); // eslint-disable-next-line default-param-last -export function encodeDisallowedCharacters(str, { escape } = {}, parse) { - if (typeof str === 'number') { - str = str.toString(); - } - - if (typeof str !== 'string' || !str.length) { - return str; - } - - if (!escape) { - return str; - } - - if (parse) { - return JSON.parse(str); - } - +export function encodeCharacters(str, { characterSet = 'reserved' } = {}) { // In ES6 you can do this quite easily by using the new ... spread operator. // This causes the string iterator (another new ES6 feature) to be used internally, // and because that iterator is designed to deal with // code points rather than UCS-2/UTF-16 code units. return [...str] .map((char) => { - if (isRrc3986Unreserved(char)) { + if (isRfc3986Unreserved(char)) { return char; } - if (isRfc3986Reserved(char) && escape === 'unsafe') { + if (isRfc3986Reserved(char) && characterSet === 'unsafe') { return char; } @@ -59,10 +43,14 @@ export default function stylize(config) { export function valueEncoder(value, escape) { if (Array.isArray(value) || (value !== null && typeof value === 'object')) { value = JSON.stringify(value); + } else if (typeof value === 'number' || typeof value === 'boolean') { + value = value.toString(); + } + + if (escape && value.length > 0) { + return encodeCharacters(value, escape === true ? {} : { characterSet: escape }); } - return encodeDisallowedCharacters(value, { - escape, - }); + return value; } function encodeArray({ key, value, style, explode, escape }) { diff --git a/test/oas3/execute/style-serializer.js b/test/oas3/execute/style-serializer.js index 67b66a593..d32c2522c 100644 --- a/test/oas3/execute/style-serializer.js +++ b/test/oas3/execute/style-serializer.js @@ -1,9 +1,9 @@ -import { encodeDisallowedCharacters } from '../../../src/execute/oas3/style-serializer.js'; +import { valueEncoder } from '../../../src/execute/oas3/style-serializer.js'; describe('OAS3 style serializer', () => { - describe('encodeDisallowedCharacters', () => { + describe('valueEncoder', () => { test('should correctly encode ASCII characters', () => { - const tested = (str) => encodeDisallowedCharacters(str, { escape: true }); + const tested = (str) => valueEncoder(str, true); expect(tested('!')).toEqual('%21'); expect(tested('#')).toEqual('%23'); @@ -28,7 +28,7 @@ describe('OAS3 style serializer', () => { }); test('should correctly encode non-ASCII characters', () => { - const tested = (str) => encodeDisallowedCharacters(str, { escape: true }); + const tested = (str) => valueEncoder(str, true); expect(tested('♥')).toEqual('%E2%99%A5'); expect(tested('テスト')).toEqual('%E3%83%86%E3%82%B9%E3%83%88'); expect(tested('𩸽')).toEqual('%F0%A9%B8%BD'); @@ -39,7 +39,7 @@ describe('OAS3 style serializer', () => { }); test('should skip encoding if `escape` is not set to true', () => { - const tested = (str) => encodeDisallowedCharacters(str); + const tested = (str) => valueEncoder(str); expect(tested('!')).toEqual('!'); expect(tested('#')).toEqual('#'); From 9357c69e4eae04475fe70e8167051745f898a549 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Mon, 8 Apr 2024 13:14:51 +0200 Subject: [PATCH 2/3] apply requested changes --- src/execute/oas3/parameter-builders.js | 2 +- src/execute/oas3/style-serializer.js | 8 +- test/oas3/execute/style-serializer.js | 151 +++++++++++++++++++++++-- 3 files changed, 149 insertions(+), 12 deletions(-) diff --git a/src/execute/oas3/parameter-builders.js b/src/execute/oas3/parameter-builders.js index 8bf8c7900..fe5193a9e 100644 --- a/src/execute/oas3/parameter-builders.js +++ b/src/execute/oas3/parameter-builders.js @@ -18,7 +18,7 @@ export function path({ req, value, parameter }) { value, style: style || 'simple', explode: explode || false, - escape: true, + escape: 'reserved', }); req.url = req.url.replace(new RegExp(`{${name}}`, 'g'), styledValue); diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index 099d71312..0617aa8b3 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -2,7 +2,7 @@ const isRfc3986Reserved = (char) => ":/?#[]@!$&'()*+,;=".indexOf(char) > -1; const isRfc3986Unreserved = (char) => /^[a-z0-9\-._~]+$/i.test(char); // eslint-disable-next-line default-param-last -export function encodeCharacters(str, { characterSet = 'reserved' } = {}) { +export function encodeCharacters(str, characterSet = 'reserved') { // In ES6 you can do this quite easily by using the new ... spread operator. // This causes the string iterator (another new ES6 feature) to be used internally, // and because that iterator is designed to deal with @@ -40,15 +40,15 @@ export default function stylize(config) { return encodePrimitive(config); } -export function valueEncoder(value, escape) { +export function valueEncoder(value, escape = false) { if (Array.isArray(value) || (value !== null && typeof value === 'object')) { value = JSON.stringify(value); } else if (typeof value === 'number' || typeof value === 'boolean') { - value = value.toString(); + value = String(value); } if (escape && value.length > 0) { - return encodeCharacters(value, escape === true ? {} : { characterSet: escape }); + return encodeCharacters(value, escape); } return value; } diff --git a/test/oas3/execute/style-serializer.js b/test/oas3/execute/style-serializer.js index d32c2522c..09f57268c 100644 --- a/test/oas3/execute/style-serializer.js +++ b/test/oas3/execute/style-serializer.js @@ -1,9 +1,11 @@ -import { valueEncoder } from '../../../src/execute/oas3/style-serializer.js'; +import { escape } from 'querystring'; + +import { encodeCharacters, valueEncoder } from '../../../src/execute/oas3/style-serializer.js'; describe('OAS3 style serializer', () => { - describe('valueEncoder', () => { + describe('encodeCharacters', () => { test('should correctly encode ASCII characters', () => { - const tested = (str) => valueEncoder(str, true); + const tested = (str) => encodeCharacters(str); expect(tested('!')).toEqual('%21'); expect(tested('#')).toEqual('%23'); @@ -28,7 +30,7 @@ describe('OAS3 style serializer', () => { }); test('should correctly encode non-ASCII characters', () => { - const tested = (str) => valueEncoder(str, true); + const tested = (str) => encodeCharacters(str); expect(tested('♥')).toEqual('%E2%99%A5'); expect(tested('テスト')).toEqual('%E3%83%86%E3%82%B9%E3%83%88'); expect(tested('𩸽')).toEqual('%F0%A9%B8%BD'); @@ -37,9 +39,105 @@ describe('OAS3 style serializer', () => { '%F0%9F%91%A9%E2%80%8D%F0%9F%91%A9%E2%80%8D%F0%9F%91%A7%E2%80%8D%F0%9F%91%A7' ); }); + }); + + describe('valueEncoder', () => { + test('should correctly encode primitive values with escape set to `reserved`', () => { + const tested = (value) => valueEncoder(value, 'reserved'); + + expect(tested(123)).toEqual('123'); + expect(tested('a#b$c%d[e]_~1')).toEqual(escape('a#b$c%d[e]_~1')); + expect(tested(false)).toEqual('false'); + }); + + test('should correctly encode primitive values with escape set to `unsafe`', () => { + const tested = (value) => valueEncoder(value, 'unsafe'); + + expect(tested(123)).toEqual('123'); + expect(tested('a#b$c%d[e]_~1')).toEqual(`a#b$c${escape('%')}d[e]_~1`); + expect(tested(false)).toEqual(escape('false')); + }); + + test('should correctly encode objects with escape set to `reserved`', () => { + const tested = (value) => valueEncoder(value, 'reserved'); + + expect(tested({ a: 'a#b$c%d[e]_~1' })).toEqual(escape('{"a":"a#b$c%d[e]_~1"}')); + expect(tested({ a: { b: { c: 'd' } } })).toEqual(escape('{"a":{"b":{"c":"d"}}}')); + expect(tested({ a: 123 })).toEqual(escape('{"a":123}')); + expect(tested({ a: false })).toEqual(escape('{"a":false}')); + }); + + test('should correctly encode objects with escape set to `unsafe`', () => { + const tested = (value) => valueEncoder(value, 'unsafe'); + + expect(tested({ a: 'a#b$c%d[e]_~1' })).toEqual( + `${escape('{"a"')}:${escape('"')}a#b$c${escape('%')}d[e]_~1${escape('"}')}` + ); + expect(tested({ a: { b: { c: 'd' } } })).toEqual( + `${escape('{"a"')}:${escape('{"b"')}:${escape('{"c"')}:${escape('"d"}}}')}` + ); + expect(tested({ a: 123 })).toEqual(`${escape('{"a"')}:${escape('123}')}`); + expect(tested({ a: false })).toEqual(`${escape('{"a"')}:${escape('false}')}`); + }); + + test('should correctly encode arrays with escape set to `reserved`', () => { + const tested = (value) => valueEncoder(value, 'reserved'); + + expect(tested([1, 2, 3])).toEqual(escape('[1,2,3]')); + expect(tested(['1', '2', 'a#b$c%d[e]_~1'])).toEqual(escape('["1","2","a#b$c%d[e]_~1"]')); + expect( + tested([ + { + a: { + b: 'c', + }, + }, + ]) + ).toEqual(escape('[{"a":{"b":"c"}}]')); + expect( + tested([ + [ + { + a: { + b: 'c', + }, + }, + ], + ]) + ).toEqual(escape('[[{"a":{"b":"c"}}]]')); + }); + + test('should correctly encode arrays with escape set to `unsafe`', () => { + const tested = (value) => valueEncoder(value, 'unsafe'); + + expect(tested([1, 2, 3])).toEqual('[1,2,3]'); + expect(tested(['1', '2', 'a#b$c%d[e]_~1'])).toEqual( + `[${escape('"1"')},${escape('"2"')},${escape('"')}a#b$c${escape('%')}d[e]_~1${escape('"')}]` + ); + expect( + tested([ + { + a: { + b: 'c', + }, + }, + ]) + ).toEqual(`[${escape('{"a"')}:${escape('{"b"')}:${escape('"c"}}')}]`); + expect( + tested([ + [ + { + a: { + b: 'c', + }, + }, + ], + ]) + ).toEqual(`[[${escape('{"a"')}:${escape('{"b"')}:${escape('"c"}}')}]]`); + }); - test('should skip encoding if `escape` is not set to true', () => { - const tested = (str) => valueEncoder(str); + test('should skip encoding if `escape` is not set', () => { + const tested = (value) => valueEncoder(value); expect(tested('!')).toEqual('!'); expect(tested('#')).toEqual('#'); @@ -61,12 +159,51 @@ describe('OAS3 style serializer', () => { expect(tested(']')).toEqual(']'); expect(tested('\n')).toEqual('\n'); - // Non-ASCII too! + // Non-ASCII expect(tested('♥')).toEqual('♥'); expect(tested('テスト')).toEqual('テスト'); expect(tested('𩸽')).toEqual('𩸽'); expect(tested('🍣')).toEqual('🍣'); expect(tested('👩‍👩‍👧‍👧')).toEqual('👩‍👩‍👧‍👧'); + + // Primitive + expect(tested(123)).toEqual('123'); + expect(tested(false)).toEqual('false'); + + // Objects + expect(tested({ a: 'a#b$c%d[e]_~1' })).toEqual('{"a":"a#b$c%d[e]_~1"}'); + expect(tested({ a: { b: { c: 'd' } } })).toEqual('{"a":{"b":{"c":"d"}}}'); + expect(tested({ a: 123 })).toEqual('{"a":123}'); + expect(tested({ a: false })).toEqual('{"a":false}'); + + // Arrays + expect(tested([1, 2, 3])).toEqual('[1,2,3]'); + expect(tested(['1', '2', 'a#b$c%d[e]_~1'])).toEqual('["1","2","a#b$c%d[e]_~1"]'); + expect( + tested([ + { + a: { + b: 'c', + }, + }, + { + d: { + e: 'f', + }, + }, + ]) + ).toEqual('[{"a":{"b":"c"}},{"d":{"e":"f"}}]'); + expect( + tested([ + [ + { + a: { + b: 'c', + }, + }, + ], + ]) + ).toEqual('[[{"a":{"b":"c"}}]]'); }); }); }); From a2c4a0e9bd631ef34b7894de3ec5eb7bcdebfc2c Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Mon, 8 Apr 2024 13:48:02 +0200 Subject: [PATCH 3/3] update tests --- test/oas3/execute/style-serializer.js | 58 ++++++++++++++------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/test/oas3/execute/style-serializer.js b/test/oas3/execute/style-serializer.js index 09f57268c..e36974441 100644 --- a/test/oas3/execute/style-serializer.js +++ b/test/oas3/execute/style-serializer.js @@ -1,5 +1,3 @@ -import { escape } from 'querystring'; - import { encodeCharacters, valueEncoder } from '../../../src/execute/oas3/style-serializer.js'; describe('OAS3 style serializer', () => { @@ -42,49 +40,53 @@ describe('OAS3 style serializer', () => { }); describe('valueEncoder', () => { - test('should correctly encode primitive values with escape set to `reserved`', () => { + test('should correctly encode primitive values with `escape` set to `reserved`', () => { const tested = (value) => valueEncoder(value, 'reserved'); expect(tested(123)).toEqual('123'); - expect(tested('a#b$c%d[e]_~1')).toEqual(escape('a#b$c%d[e]_~1')); + expect(tested('a#b$c%d[e]_~1')).toEqual('a%23b%24c%25d%5Be%5D_~1'); expect(tested(false)).toEqual('false'); }); - test('should correctly encode primitive values with escape set to `unsafe`', () => { + test('should correctly encode primitive values with `escape` set to `unsafe`', () => { const tested = (value) => valueEncoder(value, 'unsafe'); expect(tested(123)).toEqual('123'); - expect(tested('a#b$c%d[e]_~1')).toEqual(`a#b$c${escape('%')}d[e]_~1`); - expect(tested(false)).toEqual(escape('false')); + expect(tested('a#b$c%d[e]_~1')).toEqual('a#b$c%25d[e]_~1'); + expect(tested(false)).toEqual('false'); }); - test('should correctly encode objects with escape set to `reserved`', () => { + test('should correctly encode objects with `escape` set to `reserved`', () => { const tested = (value) => valueEncoder(value, 'reserved'); - expect(tested({ a: 'a#b$c%d[e]_~1' })).toEqual(escape('{"a":"a#b$c%d[e]_~1"}')); - expect(tested({ a: { b: { c: 'd' } } })).toEqual(escape('{"a":{"b":{"c":"d"}}}')); - expect(tested({ a: 123 })).toEqual(escape('{"a":123}')); - expect(tested({ a: false })).toEqual(escape('{"a":false}')); + expect(tested({ a: 'a#b$c%d[e]_~1' })).toEqual( + '%7B%22a%22%3A%22a%23b%24c%25d%5Be%5D_~1%22%7D' + ); + expect(tested({ a: { b: { c: 'd' } } })).toEqual( + '%7B%22a%22%3A%7B%22b%22%3A%7B%22c%22%3A%22d%22%7D%7D%7D' + ); + expect(tested({ a: 123 })).toEqual('%7B%22a%22%3A123%7D'); + expect(tested({ a: false })).toEqual('%7B%22a%22%3Afalse%7D'); }); - test('should correctly encode objects with escape set to `unsafe`', () => { + test('should correctly encode objects with `escape` set to `unsafe`', () => { const tested = (value) => valueEncoder(value, 'unsafe'); - expect(tested({ a: 'a#b$c%d[e]_~1' })).toEqual( - `${escape('{"a"')}:${escape('"')}a#b$c${escape('%')}d[e]_~1${escape('"}')}` - ); + expect(tested({ a: 'a#b$c%d[e]_~1' })).toEqual('%7B%22a%22:%22a#b$c%25d[e]_~1%22%7D'); expect(tested({ a: { b: { c: 'd' } } })).toEqual( - `${escape('{"a"')}:${escape('{"b"')}:${escape('{"c"')}:${escape('"d"}}}')}` + '%7B%22a%22:%7B%22b%22:%7B%22c%22:%22d%22%7D%7D%7D' ); - expect(tested({ a: 123 })).toEqual(`${escape('{"a"')}:${escape('123}')}`); - expect(tested({ a: false })).toEqual(`${escape('{"a"')}:${escape('false}')}`); + expect(tested({ a: 123 })).toEqual('%7B%22a%22:123%7D'); + expect(tested({ a: false })).toEqual('%7B%22a%22:false%7D'); }); - test('should correctly encode arrays with escape set to `reserved`', () => { + test('should correctly encode arrays with `escape` set to `reserved`', () => { const tested = (value) => valueEncoder(value, 'reserved'); - expect(tested([1, 2, 3])).toEqual(escape('[1,2,3]')); - expect(tested(['1', '2', 'a#b$c%d[e]_~1'])).toEqual(escape('["1","2","a#b$c%d[e]_~1"]')); + expect(tested([1, 2, 3])).toEqual('%5B1%2C2%2C3%5D'); + expect(tested(['1', '2', 'a#b$c%d[e]_~1'])).toEqual( + '%5B%221%22%2C%222%22%2C%22a%23b%24c%25d%5Be%5D_~1%22%5D' + ); expect( tested([ { @@ -93,7 +95,7 @@ describe('OAS3 style serializer', () => { }, }, ]) - ).toEqual(escape('[{"a":{"b":"c"}}]')); + ).toEqual('%5B%7B%22a%22%3A%7B%22b%22%3A%22c%22%7D%7D%5D'); expect( tested([ [ @@ -104,15 +106,15 @@ describe('OAS3 style serializer', () => { }, ], ]) - ).toEqual(escape('[[{"a":{"b":"c"}}]]')); + ).toEqual('%5B%5B%7B%22a%22%3A%7B%22b%22%3A%22c%22%7D%7D%5D%5D'); }); - test('should correctly encode arrays with escape set to `unsafe`', () => { + test('should correctly encode arrays with `escape` set to `unsafe`', () => { const tested = (value) => valueEncoder(value, 'unsafe'); expect(tested([1, 2, 3])).toEqual('[1,2,3]'); expect(tested(['1', '2', 'a#b$c%d[e]_~1'])).toEqual( - `[${escape('"1"')},${escape('"2"')},${escape('"')}a#b$c${escape('%')}d[e]_~1${escape('"')}]` + '[%221%22,%222%22,%22a#b$c%25d[e]_~1%22]' ); expect( tested([ @@ -122,7 +124,7 @@ describe('OAS3 style serializer', () => { }, }, ]) - ).toEqual(`[${escape('{"a"')}:${escape('{"b"')}:${escape('"c"}}')}]`); + ).toEqual('[%7B%22a%22:%7B%22b%22:%22c%22%7D%7D]'); expect( tested([ [ @@ -133,7 +135,7 @@ describe('OAS3 style serializer', () => { }, ], ]) - ).toEqual(`[[${escape('{"a"')}:${escape('{"b"')}:${escape('"c"}}')}]]`); + ).toEqual('[[%7B%22a%22:%7B%22b%22:%22c%22%7D%7D]]'); }); test('should skip encoding if `escape` is not set', () => {