From 54a1b759c0baa035022697852350b2476ff9fe53 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Mon, 2 Dec 2024 14:49:32 +0100 Subject: [PATCH 1/5] fix(execute): fix encoding of object properties with null value --- src/execute/oas3/style-serializer.js | 2 +- test/oas3/execute/main.js | 72 ++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index 0617aa8b3..ea0c77f20 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -41,7 +41,7 @@ export default function stylize(config) { } export function valueEncoder(value, escape = false) { - if (Array.isArray(value) || (value !== null && typeof value === 'object')) { + if (Array.isArray(value) || typeof value === 'object') { value = JSON.stringify(value); } else if (typeof value === 'number' || typeof value === 'boolean') { value = String(value); diff --git a/test/oas3/execute/main.js b/test/oas3/execute/main.js index 88dc06a5c..1612ccf89 100644 --- a/test/oas3/execute/main.js +++ b/test/oas3/execute/main.js @@ -664,6 +664,78 @@ describe('buildRequest - OpenAPI Specification 3.0', () => { }); }); + it('should encode objects with a property with `null` value', () => { + 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: { + pathPartial: { + a: null, + }, + query: { + b: null, + }, + FooHeader: { + c: null, + }, + myCookie: { + d: null, + }, + }, + }); + + expect(req).toEqual({ + method: 'POST', + url: `/a=null?b=null`, + credentials: 'same-origin', + headers: { + FooHeader: 'c=null', + Cookie: 'myCookie=d,null', + }, + }); + }); + it('should encode arrays of arrays and objects', () => { const req = buildRequest({ spec: { From 3d060ca1d5e8cd5e8fcb4f91a79e118146e25ecf Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Mon, 2 Dec 2024 16:09:48 +0100 Subject: [PATCH 2/5] fix(execute): fix undefined values causing errors --- src/execute/oas3/style-serializer.js | 2 +- test/oas3/execute/main.js | 64 ++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index ea0c77f20..2df1c2633 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -47,7 +47,7 @@ export function valueEncoder(value, escape = false) { value = String(value); } - if (escape && value.length > 0) { + if (escape && typeof value === 'string' && value.length > 0) { return encodeCharacters(value, escape); } return value; diff --git a/test/oas3/execute/main.js b/test/oas3/execute/main.js index 1612ccf89..2d534a24a 100644 --- a/test/oas3/execute/main.js +++ b/test/oas3/execute/main.js @@ -736,6 +736,70 @@ describe('buildRequest - OpenAPI Specification 3.0', () => { }); }); + it('should encode arrays with `undefined` items', () => { + const req = buildRequest({ + spec: { + openapi: '3.0.0', + paths: { + '/{pathPartial}': { + post: { + operationId: 'myOp', + parameters: [ + { + name: 'query', + in: 'query', + schema: { + type: 'array', + }, + }, + { + name: 'FooHeader', + in: 'header', + schema: { + type: 'array', + }, + explode: true, + }, + { + name: 'pathPartial', + in: 'path', + schema: { + type: 'array', + }, + explode: true, + }, + { + name: 'myCookie', + in: 'cookie', + schema: { + type: 'array', + }, + }, + ], + }, + }, + }, + }, + operationId: 'myOp', + parameters: { + pathPartial: [undefined], + query: [undefined], + FooHeader: [undefined], + myCookie: [undefined], + }, + }); + + expect(req).toEqual({ + method: 'POST', + url: `/?query=`, + credentials: 'same-origin', + headers: { + FooHeader: '', + Cookie: 'myCookie=', + }, + }); + }); + it('should encode arrays of arrays and objects', () => { const req = buildRequest({ spec: { From 4044833a1fd21268101801012819d26537161300 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Tue, 3 Dec 2024 09:16:11 +0100 Subject: [PATCH 3/5] fix: use empty string instead of stringifed null --- src/execute/oas3/style-serializer.js | 12 ++--- test/oas3/execute/main.js | 71 ++++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index 2df1c2633..d5c1629a9 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -41,7 +41,7 @@ export default function stylize(config) { } export function valueEncoder(value, escape = false) { - if (Array.isArray(value) || typeof value === 'object') { + if (Array.isArray(value) || (value !== null && typeof value === 'object')) { value = JSON.stringify(value); } else if (typeof value === 'number' || typeof value === 'boolean') { value = String(value); @@ -96,7 +96,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'simple') { return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr], escape); + const val = valueEncoder(value[curr], escape) ?? ''; const middleChar = explode ? '=' : ','; const prefix = prev ? `${prev},` : ''; @@ -106,7 +106,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'label') { return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr], escape); + const val = valueEncoder(value[curr], escape) ?? ''; const middleChar = explode ? '=' : '.'; const prefix = prev ? `${prev}.` : '.'; @@ -116,7 +116,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'matrix' && explode) { return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr], escape); + const val = valueEncoder(value[curr], escape) ?? ''; const prefix = prev ? `${prev};` : ';'; return `${prefix}${curr}=${val}`; @@ -126,7 +126,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'matrix') { // no explode return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr], escape); + const val = valueEncoder(value[curr], escape) ?? ''; const prefix = prev ? `${prev},` : `;${key}=`; return `${prefix}${curr},${val}`; @@ -135,7 +135,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'form') { return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr], escape); + const val = valueEncoder(value[curr], escape) ?? ''; const prefix = prev ? `${prev}${explode ? '&' : ','}` : ''; const separator = explode ? '=' : ','; diff --git a/test/oas3/execute/main.js b/test/oas3/execute/main.js index 2d534a24a..38032c985 100644 --- a/test/oas3/execute/main.js +++ b/test/oas3/execute/main.js @@ -727,11 +727,11 @@ describe('buildRequest - OpenAPI Specification 3.0', () => { expect(req).toEqual({ method: 'POST', - url: `/a=null?b=null`, + url: `/a=?b=`, credentials: 'same-origin', headers: { - FooHeader: 'c=null', - Cookie: 'myCookie=d,null', + FooHeader: 'c=', + Cookie: 'myCookie=d,', }, }); }); @@ -758,7 +758,6 @@ describe('buildRequest - OpenAPI Specification 3.0', () => { schema: { type: 'array', }, - explode: true, }, { name: 'pathPartial', @@ -766,7 +765,6 @@ describe('buildRequest - OpenAPI Specification 3.0', () => { schema: { type: 'array', }, - explode: true, }, { name: 'myCookie', @@ -800,6 +798,69 @@ describe('buildRequest - OpenAPI Specification 3.0', () => { }); }); + it('should encode arrays with multiple `undefined` items', () => { + const req = buildRequest({ + spec: { + openapi: '3.0.0', + paths: { + '/{pathPartial}': { + post: { + operationId: 'myOp', + parameters: [ + { + name: 'query', + in: 'query', + schema: { + type: 'array', + }, + explode: false, + }, + { + name: 'FooHeader', + in: 'header', + schema: { + type: 'array', + }, + }, + { + name: 'pathPartial', + in: 'path', + schema: { + type: 'array', + }, + }, + { + name: 'myCookie', + in: 'cookie', + schema: { + type: 'array', + }, + }, + ], + }, + }, + }, + }, + operationId: 'myOp', + parameters: { + pathPartial: [undefined, undefined, undefined], + query: [undefined, undefined, undefined], + FooHeader: [undefined, undefined, undefined], + myCookie: [undefined, undefined, undefined], + }, + }); + + expect(req).toEqual({ + method: 'POST', + url: `/,,?query=,,`, + credentials: 'same-origin', + headers: { + FooHeader: ',,', + Cookie: 'myCookie=,,', + }, + }); + }); + it('should encode arrays of arrays and objects', () => { const req = buildRequest({ spec: { From 66f42c8e55f589b64f1921f52e7ca35d798bc074 Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Tue, 3 Dec 2024 09:22:29 +0100 Subject: [PATCH 4/5] refactor: move empty string return to valueEncoder --- src/execute/oas3/style-serializer.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/execute/oas3/style-serializer.js b/src/execute/oas3/style-serializer.js index d5c1629a9..13c6a4e6c 100644 --- a/src/execute/oas3/style-serializer.js +++ b/src/execute/oas3/style-serializer.js @@ -50,7 +50,7 @@ export function valueEncoder(value, escape = false) { if (escape && typeof value === 'string' && value.length > 0) { return encodeCharacters(value, escape); } - return value; + return value ?? ''; } function encodeArray({ key, value, style, explode, escape }) { @@ -96,7 +96,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'simple') { return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr], escape) ?? ''; + const val = valueEncoder(value[curr], escape); const middleChar = explode ? '=' : ','; const prefix = prev ? `${prev},` : ''; @@ -106,7 +106,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'label') { return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr], escape) ?? ''; + const val = valueEncoder(value[curr], escape); const middleChar = explode ? '=' : '.'; const prefix = prev ? `${prev}.` : '.'; @@ -116,7 +116,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'matrix' && explode) { return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr], escape) ?? ''; + const val = valueEncoder(value[curr], escape); const prefix = prev ? `${prev};` : ';'; return `${prefix}${curr}=${val}`; @@ -126,7 +126,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'matrix') { // no explode return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr], escape) ?? ''; + const val = valueEncoder(value[curr], escape); const prefix = prev ? `${prev},` : `;${key}=`; return `${prefix}${curr},${val}`; @@ -135,7 +135,7 @@ function encodeObject({ key, value, style, explode, escape }) { if (style === 'form') { return valueKeys.reduce((prev, curr) => { - const val = valueEncoder(value[curr], escape) ?? ''; + const val = valueEncoder(value[curr], escape); const prefix = prev ? `${prev}${explode ? '&' : ','}` : ''; const separator = explode ? '=' : ','; From b507e83dcba89fc19d73adfc98cd080e3e4bcdef Mon Sep 17 00:00:00 2001 From: Oliwia Rogala Date: Tue, 3 Dec 2024 10:37:20 +0100 Subject: [PATCH 5/5] chore: remove empty string return from stringifyQuery --- src/http/serializers/request/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/http/serializers/request/index.js b/src/http/serializers/request/index.js index c6ddcc18f..093d832f7 100644 --- a/src/http/serializers/request/index.js +++ b/src/http/serializers/request/index.js @@ -40,9 +40,7 @@ function buildFormData(reqForm) { export const stringifyQuery = (queryObject, { encode = true } = {}) => { const buildNestedParams = (params, key, value) => { - if (value == null) { - params.append(key, ''); - } else if (Array.isArray(value)) { + if (Array.isArray(value)) { value.reduce((acc, v) => buildNestedParams(params, key, v), params); } else if (value instanceof Date) { params.append(key, value.toISOString());