From e71b86801f76f2f0cd59ae8f5a5c51fa0a0a7d19 Mon Sep 17 00:00:00 2001 From: Mariano Cano Date: Thu, 1 Aug 2024 17:27:02 -0700 Subject: [PATCH] feat: add object with deepObject style in query parameters This commit adds support for using objects with the deepObject style in the TryIt component. A parameter `id` with the value `{"role": "admin", "firstName": "Alex"}` will create the query parameters `id[role]=admin&id[firstName]=Alex`. --- .../operations/operation-parameters.ts | 8 +++++ .../src/__fixtures__/operations/put-todos.ts | 8 +++++ .../src/components/TryIt/TryIt.spec.tsx | 6 ++++ .../components/TryIt/build-request.spec.ts | 5 +++- .../src/components/TryIt/build-request.ts | 29 +++++++++++++------ 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/packages/elements-core/src/__fixtures__/operations/operation-parameters.ts b/packages/elements-core/src/__fixtures__/operations/operation-parameters.ts index 672a5f2ef..144b03d59 100644 --- a/packages/elements-core/src/__fixtures__/operations/operation-parameters.ts +++ b/packages/elements-core/src/__fixtures__/operations/operation-parameters.ts @@ -207,6 +207,14 @@ export const httpOperation: IHttpOperation = { style: HttpParamStyles.Form, explode: false, }, + { + schema: { + type: 'object', + }, + name: 'deep_object', + style: HttpParamStyles.DeepObject, + explode: true, + }, { schema: { type: 'boolean', diff --git a/packages/elements-core/src/__fixtures__/operations/put-todos.ts b/packages/elements-core/src/__fixtures__/operations/put-todos.ts index 176ce51ff..d5ebc272c 100644 --- a/packages/elements-core/src/__fixtures__/operations/put-todos.ts +++ b/packages/elements-core/src/__fixtures__/operations/put-todos.ts @@ -411,6 +411,14 @@ export const httpOperation: IHttpOperation = { name: 'pairs', style: HttpParamStyles.Form, }, + { + id: '?http-query-pagination?', + schema: { + type: 'object', + }, + name: 'pagination', + style: HttpParamStyles.DeepObject, + }, { id: '?http-query-items?', schema: { diff --git a/packages/elements-core/src/components/TryIt/TryIt.spec.tsx b/packages/elements-core/src/components/TryIt/TryIt.spec.tsx index 6f1ee8c30..162d8cc64 100644 --- a/packages/elements-core/src/components/TryIt/TryIt.spec.tsx +++ b/packages/elements-core/src/components/TryIt/TryIt.spec.tsx @@ -204,6 +204,7 @@ describe('TryIt', () => { 'limit*', 'super_duper_long_parameter_name_with_unnecessary_text*', 'completed', + 'deep_object', 'default_style_items', 'items', 'items_not_exploded', @@ -283,6 +284,9 @@ describe('TryIt', () => { const pairsField = screen.getByLabelText('pairs'); userEvent.type(pairsField, '{ "nestedKey": "nestedValue" }'); + const pagination = screen.getByLabelText('pagination'); + userEvent.type(pagination, '{ "first": 50, "after": "cursor" }'); + const itemsField = screen.getByLabelText('items'); userEvent.type(itemsField, '["first", "second"]'); @@ -312,6 +316,8 @@ describe('TryIt', () => { expect(queryParams.get('optional_value_with_default')).toBeNull(); expect(queryParams.get('nestedKey')).toBe('nestedValue'); expect(queryParams.get('pairs')).toBeNull(); + expect(queryParams.get('pagination[first]')).toBe('50'); + expect(queryParams.get('pagination[after]')).toBe('cursor'); expect(queryParams.getAll('items')).toEqual(['first', 'second']); // assert that headers are passed const headers = new Headers(fetchMock.mock.calls[0][1]!.headers); diff --git a/packages/elements-core/src/components/TryIt/build-request.spec.ts b/packages/elements-core/src/components/TryIt/build-request.spec.ts index 366ee6d9f..d8a52ee26 100644 --- a/packages/elements-core/src/components/TryIt/build-request.spec.ts +++ b/packages/elements-core/src/components/TryIt/build-request.spec.ts @@ -36,7 +36,7 @@ describe('Build Request', () => { }).toThrowError('JSON array expected'); }); - it('Supports form style', () => { + it('Supports form and deepObject style', () => { const params = getQueryParams({ httpOperation, parameterValues: { @@ -46,6 +46,7 @@ describe('Build Request', () => { default_style_items: '["first","second"]', nested: '{"key":"value"}', nested_not_exploded: '{"key":"value"}', + deep_object: '{"key":"value", "number": 2}', }, }); @@ -58,6 +59,8 @@ describe('Build Request', () => { { name: 'default_style_items', value: 'second' }, { name: 'key', value: 'value' }, { name: 'nested_not_exploded', value: 'key,value' }, + { name: 'deep_object[key]', value: 'value' }, + { name: 'deep_object[number]', value: '2' }, ]); }); diff --git a/packages/elements-core/src/components/TryIt/build-request.ts b/packages/elements-core/src/components/TryIt/build-request.ts index 19c45aad5..0c1f9ea37 100644 --- a/packages/elements-core/src/components/TryIt/build-request.ts +++ b/packages/elements-core/src/components/TryIt/build-request.ts @@ -71,7 +71,7 @@ export const getQueryParams = ({ const explode = param.explode ?? true; - if (param.schema?.type === 'object' && param.style === 'form' && value) { + if (param.schema?.type === 'object' && value) { let nested: Dictionary; try { nested = JSON.parse(value); @@ -80,15 +80,26 @@ export const getQueryParams = ({ throw new Error(`Cannot use param value "${value}". JSON object expected.`); } - if (explode) { - acc.push(...Object.entries(nested).map(([name, value]) => ({ name, value: value.toString() }))); + if (param.style === 'form') { + if (explode) { + acc.push(...Object.entries(nested).map(([name, value]) => ({ name, value: value.toString() }))); + } else { + acc.push({ + name: param.name, + value: Object.entries(nested) + .map(entry => entry.join(',')) + .join(','), + }); + } + } else if (param.style === 'deepObject') { + acc.push( + ...Object.entries(nested).map(([name, value]) => ({ + name: `${param.name}[${name}]`, + value: value.toString(), + })), + ); } else { - acc.push({ - name: param.name, - value: Object.entries(nested) - .map(entry => entry.join(',')) - .join(','), - }); + acc.push({ name: param.name, value }); } } else if (param.schema?.type === 'array' && value) { let nested: string[];