diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index 7033b8d6c..636043284 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -6,6 +6,7 @@ export function encodeDisallowedCharacters(str, { escape } = {}, parse) { if (typeof str === 'number') { str = str.toString(); } + if (typeof str !== 'string' || !str.length) { return str; } @@ -55,23 +56,27 @@ export default function stylize(config) { return encodePrimitive(config); } -function encodeArray({ key, value, style, explode, escape }) { - const valueEncoder = (str) => - encodeDisallowedCharacters(str, { - escape, - }); +export function valueEncoder(value, escape) { + if (Array.isArray(value) || (value !== null && typeof value === 'object')) { + value = JSON.stringify(value); + } + return encodeDisallowedCharacters(value, { + escape, + }); +} +function encodeArray({ key, value, style, explode, escape }) { if (style === 'simple') { - return value.map((val) => valueEncoder(val)).join(','); + return value.map((val) => valueEncoder(val, escape)).join(','); } if (style === 'label') { - return `.${value.map((val) => valueEncoder(val)).join('.')}`; + return `.${value.map((val) => valueEncoder(val, escape)).join('.')}`; } if (style === 'matrix') { return value - .map((val) => valueEncoder(val)) + .map((val) => valueEncoder(val, escape)) .reduce((prev, curr) => { if (!prev || explode) { return `${prev || ''};${key}=${curr}`; @@ -82,33 +87,28 @@ function encodeArray({ key, value, style, explode, escape }) { if (style === 'form') { const after = explode ? `&${key}=` : ','; - return value.map((val) => valueEncoder(val)).join(after); + return value.map((val) => valueEncoder(val, escape)).join(after); } if (style === 'spaceDelimited') { const after = explode ? `${key}=` : ''; - return value.map((val) => valueEncoder(val)).join(` ${after}`); + return value.map((val) => valueEncoder(val, escape)).join(` ${after}`); } if (style === 'pipeDelimited') { const after = explode ? `${key}=` : ''; - return value.map((val) => valueEncoder(val)).join(`|${after}`); + return value.map((val) => valueEncoder(val, escape)).join(`|${after}`); } return undefined; } function encodeObject({ key, value, style, explode, escape }) { - const valueEncoder = (str) => - encodeDisallowedCharacters(str, { - escape, - }); - const valueKeys = Object.keys(value); if (style === 'simple') { return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr]); + const val = valueEncoder(value[curr], escape); const middleChar = explode ? '=' : ','; const prefix = prev ? `${prev},` : ''; @@ -118,7 +118,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'label') { return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr]); + const val = valueEncoder(value[curr], escape); const middleChar = explode ? '=' : '.'; const prefix = prev ? `${prev}.` : '.'; @@ -128,7 +128,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'matrix' && explode) { return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr]); + const val = valueEncoder(value[curr], escape); const prefix = prev ? `${prev};` : ';'; return `${prefix}${curr}=${val}`; @@ -138,7 +138,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'matrix') { // no explode return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr]); + const val = valueEncoder(value[curr], escape); const prefix = prev ? `${prev},` : `;${key}=`; return `${prefix}${curr},${val}`; @@ -147,7 +147,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'form') { return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr]); + const val = valueEncoder(value[curr], escape); const prefix = prev ? `${prev}${explode ? '&' : ','}` : ''; const separator = explode ? '=' : ','; @@ -159,29 +159,24 @@ function encodeObject({ key, value, style, explode, escape }) { } function encodePrimitive({ key, value, style, escape }) { - const valueEncoder = (str) => - encodeDisallowedCharacters(str, { - escape, - }); - if (style === 'simple') { - return valueEncoder(value); + return valueEncoder(value, escape); } if (style === 'label') { - return `.${valueEncoder(value)}`; + return `.${valueEncoder(value, escape)}`; } if (style === 'matrix') { - return `;${key}=${valueEncoder(value)}`; + return `;${key}=${valueEncoder(value, escape)}`; } if (style === 'form') { - return valueEncoder(value); + return valueEncoder(value, escape); } if (style === 'deepObject') { - return valueEncoder(value, {}, true); + return valueEncoder(value, escape); } return undefined; diff --git a/src/http/index.js b/src/http/index.js index 260de374d..c3eec26da 100644 --- a/src/http/index.js +++ b/src/http/index.js @@ -2,7 +2,7 @@ import qs from 'qs'; import jsYaml from 'js-yaml'; import '../helpers/fetch-polyfill.node.js'; -import { encodeDisallowedCharacters } from '../execute/oas3/style-serializer.js'; +import { valueEncoder } from '../execute/oas3/style-serializer.js'; // For testing export const self = { @@ -319,8 +319,9 @@ function formatKeyValueBySerializationOption(key, value, skipEncoding, serializa : serializationOption && serializationOption.allowReserved ? 'unsafe' : 'reserved'; - const encodeFn = (v) => encodeDisallowedCharacters(v, { escape }); - const encodeKeyFn = skipEncoding ? (k) => k : (k) => encodeDisallowedCharacters(k, { escape }); + + const encodeFn = (v) => valueEncoder(v, escape); + const encodeKeyFn = skipEncoding ? (k) => k : (k) => encodeFn(k); // Primitive if (typeof value !== 'object') { diff --git a/test/oas3/execute/main.js b/test/oas3/execute/main.js index 0d7bbc2e4..50796a634 100644 --- a/test/oas3/execute/main.js +++ b/test/oas3/execute/main.js @@ -572,6 +572,226 @@ describe('buildRequest - OpenAPI Specification 3.0', () => { }); }); + describe('`schema` parameters', () => { + it('should encode JSON values provided as objects', () => { + const req = buildRequest({ + spec: { + openapi: '3.0.0', + paths: { + '/{pathPartial}': { + post: { + operationId: 'myOp', + parameters: [ + { + name: 'query', + in: 'query', + schema: { + type: 'object', + }, + }, + { + name: 'FooHeader', + in: 'header', + schema: { + type: 'object', + }, + explode: true, + }, + { + name: 'pathPartial', + in: 'path', + schema: { + type: 'object', + }, + explode: true, + }, + { + name: 'myCookie', + in: 'cookie', + schema: { + type: 'object', + }, + }, + ], + }, + }, + }, + }, + operationId: 'myOp', + parameters: { + query: { + a: { + b: { + c: 'd', + }, + }, + }, + FooHeader: { + a: { + b: { + c: { + d: 'e', + }, + }, + }, + }, + pathPartial: { + foo: { + bar: { baz: 'qux' }, + }, + a: { + b: { + c: 'd', + }, + }, + }, + myCookie: { + foo: { + bar: { baz: 'qux' }, + }, + }, + }, + }); + + expect(req).toEqual({ + method: 'POST', + url: `/foo=${escape('{"bar":{"baz":"qux"}}')},a=${escape('{"b":{"c":"d"}}')}?a=${escape('{"b":{"c":"d"}}')}`, + credentials: 'same-origin', + headers: { + FooHeader: 'a={"b":{"c":{"d":"e"}}}', + Cookie: 'myCookie=foo,{"bar":{"baz":"qux"}}', + }, + }); + }); + + it('should encode arrays of arrays and objects', () => { + const req = buildRequest({ + spec: { + openapi: '3.0.0', + paths: { + '/': { + post: { + operationId: 'myOp', + parameters: [ + { + name: 'arrayOfObjects', + in: 'query', + schema: { + type: 'array', + items: { + type: 'object', + }, + }, + explode: false, + }, + { + name: 'arrayOfArrays', + in: 'query', + schema: { + type: 'array', + items: { + type: 'array', + items: { + type: 'object', + }, + }, + }, + explode: false, + }, + { + name: 'headerArrayOfObjects', + in: 'header', + schema: { + type: 'array', + items: { + type: 'object', + }, + }, + }, + { + name: 'headerArrayOfArrays', + in: 'header', + schema: { + type: 'array', + items: { + type: 'array', + items: { + type: 'object', + }, + }, + }, + }, + ], + }, + }, + }, + }, + operationId: 'myOp', + parameters: { + arrayOfObjects: [ + { + a: { + b: 'c', + }, + }, + { + d: { + e: 'f', + }, + }, + ], + arrayOfArrays: [ + [ + { + a: { + b: 'c', + }, + }, + ], + ], + headerArrayOfObjects: [ + { + a: { + b: 'c', + }, + }, + { + d: { + e: 'f', + }, + }, + ], + headerArrayOfArrays: [ + [ + { + a: { + b: 'c', + }, + }, + ], + [ + { + d: { + e: 'f', + }, + }, + ], + ], + }, + }); + + expect(req).toEqual({ + method: 'POST', + url: `/?arrayOfObjects=${escape('{"a":{"b":"c"}}')},${escape('{"d":{"e":"f"}}')}&arrayOfArrays=${escape('[{"a":{"b":"c"}}]')}`, + credentials: 'same-origin', + headers: { + headerArrayOfObjects: '{"a":{"b":"c"}},{"d":{"e":"f"}}', + headerArrayOfArrays: '[{"a":{"b":"c"}}],[{"d":{"e":"f"}}]', + }, + }); + }); + }); + describe('`content` parameters', () => { it('should serialize JSON values provided as objects', () => { const req = buildRequest({