From f6edc85bd4123f18f6ecacb04e9ab207a462f67d Mon Sep 17 00:00:00 2001 From: Antoine Sein Date: Wed, 24 Jan 2024 15:34:54 +0100 Subject: [PATCH 1/2] Provide options for unexploded parameters --- .../rest/v1/active-number/active-number-api.ts | 4 +++- .../v1/available-number/available-number-api.ts | 2 +- packages/sdk-client/src/api/api-client.ts | 14 ++++++++++---- .../src/client/api-client-pagination-helper.ts | 5 ++++- packages/sdk-client/tests/api/api-client.test.ts | 15 +++++++++++++-- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/numbers/src/rest/v1/active-number/active-number-api.ts b/packages/numbers/src/rest/v1/active-number/active-number-api.ts index 8f2296f1..92dc1aca 100644 --- a/packages/numbers/src/rest/v1/active-number/active-number-api.ts +++ b/packages/numbers/src/rest/v1/active-number/active-number-api.ts @@ -139,7 +139,9 @@ export class ActiveNumberApi extends NumbersApi { const listPromise = buildPageResultPromise( this.client, requestOptionsPromise, - operationProperties); + operationProperties, + false, + ';'); // Add properties to the Promise to offer the possibility to use it as an iterator Object.assign( diff --git a/packages/numbers/src/rest/v1/available-number/available-number-api.ts b/packages/numbers/src/rest/v1/available-number/available-number-api.ts index 86afff9f..bc7d48a0 100644 --- a/packages/numbers/src/rest/v1/available-number/available-number-api.ts +++ b/packages/numbers/src/rest/v1/available-number/available-number-api.ts @@ -122,7 +122,7 @@ export class AvailableNumberApi extends NumbersApi { headers, body || undefined, ); - const url = this.client.prepareUrl(requestOptions.basePath, requestOptions.queryParams); + const url = this.client.prepareUrl(requestOptions.basePath, requestOptions.queryParams, false, ';'); return this.client.processCall({ url, diff --git a/packages/sdk-client/src/api/api-client.ts b/packages/sdk-client/src/api/api-client.ts index 59f4c636..c0f219ba 100644 --- a/packages/sdk-client/src/api/api-client.ts +++ b/packages/sdk-client/src/api/api-client.ts @@ -77,7 +77,7 @@ export class ApiClient { const prop = data[name]; acc[name] = typeof prop.toJSON === 'function' ? prop.toJSON() - : Array.isArray(prop) ? prop.join(';') : prop.toString(); + : Array.isArray(prop) ? prop.join(',') : prop.toString(); return acc; }, {} as { [p in keyof T]: string }, @@ -128,16 +128,18 @@ export class ApiClient { * @param {string} url - The base url to be used. * @param {Object.} queryParameters - Key-value pair with the parameters. If the value is undefined, the key is dropped. * @param {boolean} repeatParamArray - create as many single parameters with each value of the array + * @param {string} unexplodedSeparator - the separator to use between values when `explode` is false (default is ',') * @return {string} The prepared URL as a string. */ prepareUrl( url: string, queryParameters: { [key: string]: string | undefined } = {}, repeatParamArray?: boolean, + unexplodedSeparator?: string, ): string { const queryPart = Object.keys(queryParameters) .filter((name) => typeof queryParameters[name] !== 'undefined') - .map((name) => this.formatQueryParameter(name, queryParameters, repeatParamArray)) + .map((name) => this.formatQueryParameter(name, queryParameters, repeatParamArray, unexplodedSeparator)) .join('&'); const paramsPrefix = url.indexOf('?') > -1 ? '&' : '?'; @@ -150,20 +152,24 @@ export class ApiClient { * @param {string} name - The parameter name * @param {Object.} queryParameters - Key-value pair with the parameters. If the value is undefined, the key is dropped. * @param {boolean}repeatParamArray - Create as many single parameters with each value of the array + * @param {string} unexplodedSeparator - the separator to use between values when `explode` is false (default is `,`) * @return {string} The query parameter formatted as required by the API */ private formatQueryParameter = ( name: string, queryParameters: { [key: string]: string | undefined } = {}, repeatParamArray?: boolean, + unexplodedSeparator?: string, ): string => { const defaultFormat = `${name}=${queryParameters[name]!}`; if(repeatParamArray) { const parameterValue = queryParameters[name]; - if (parameterValue && parameterValue.indexOf(';') > 0) { - const parameterValues = parameterValue.split(';'); + if (parameterValue && parameterValue.indexOf(',') > 0) { + const parameterValues = parameterValue.split(','); return parameterValues.map((value) => `${name}=${value}`).join('&'); } + } else if (unexplodedSeparator !== undefined) { + return defaultFormat.replaceAll(',', unexplodedSeparator); } return defaultFormat; }; diff --git a/packages/sdk-client/src/client/api-client-pagination-helper.ts b/packages/sdk-client/src/client/api-client-pagination-helper.ts index c2d0b9e0..d101523f 100644 --- a/packages/sdk-client/src/client/api-client-pagination-helper.ts +++ b/packages/sdk-client/src/client/api-client-pagination-helper.ts @@ -206,10 +206,13 @@ export const buildPageResultPromise = async ( client: ApiClient, requestOptionsPromise: Promise, operationProperties: PaginatedApiProperties, + repeatParamArray?: boolean, + unexplodedSeparator?: string, ): Promise> => { // Await the promise in this async method and store the result in client so that they can be reused const requestOptions = await requestOptionsPromise; - const url = client.prepareUrl(requestOptions.basePath, requestOptions.queryParams); + const url = client.prepareUrl( + requestOptions.basePath, requestOptions.queryParams, repeatParamArray, unexplodedSeparator); return client.processCallWithPagination({ url, diff --git a/packages/sdk-client/tests/api/api-client.test.ts b/packages/sdk-client/tests/api/api-client.test.ts index 9b46172d..4f495be4 100644 --- a/packages/sdk-client/tests/api/api-client.test.ts +++ b/packages/sdk-client/tests/api/api-client.test.ts @@ -26,10 +26,21 @@ describe('API client', () => { const url = 'https://example.com'; const parameters = { foo: 'fooValue', - bar: '1;2', + bar: '1,2', baz: undefined, }; const formattedUrl = apiClient.prepareUrl(url, parameters); + expect(formattedUrl).toBe('https://example.com?foo=fooValue&bar=1,2'); + }); + + it('should format the URL with array parameters and a custom separator', () => { + const url = 'https://example.com'; + const parameters = { + foo: 'fooValue', + bar: '1,2', + baz: undefined, + }; + const formattedUrl = apiClient.prepareUrl(url, parameters, false, ';'); expect(formattedUrl).toBe('https://example.com?foo=fooValue&bar=1;2'); }); @@ -37,7 +48,7 @@ describe('API client', () => { const url = 'https://example.com'; const parameters = { foo: 'fooValue', - bar: '1;2', + bar: '1,2', baz: undefined, }; const formattedUrl = apiClient.prepareUrl(url, parameters, true); From 101b12786a905ab16eb5804ba168adf234ffae40 Mon Sep 17 00:00:00 2001 From: Antoine Sein Date: Wed, 7 Feb 2024 14:22:50 +0100 Subject: [PATCH 2/2] Adapt parameters formating to match server's expectations --- .../src/numbers/active/list.ts | 1 + .../src/numbers/available/list.ts | 1 + .../v1/active-number/active-number-api.ts | 7 ++--- .../available-number/available-number-api.ts | 2 +- packages/sdk-client/src/api/api-client.ts | 29 ++++++++----------- .../client/api-client-pagination-helper.ts | 3 +- .../sdk-client/tests/api/api-client.test.ts | 27 +++++------------ 7 files changed, 27 insertions(+), 43 deletions(-) diff --git a/examples/simple-examples/src/numbers/active/list.ts b/examples/simple-examples/src/numbers/active/list.ts index aed4151b..9c4d54a8 100644 --- a/examples/simple-examples/src/numbers/active/list.ts +++ b/examples/simple-examples/src/numbers/active/list.ts @@ -21,6 +21,7 @@ const populateActiveNumbersList = ( const requestData: ListActiveNumbersRequestData = { regionCode: 'US', type: 'LOCAL', + capability: 'SMS', pageSize: 2, }; diff --git a/examples/simple-examples/src/numbers/available/list.ts b/examples/simple-examples/src/numbers/available/list.ts index 2ca680a4..f42adbb8 100644 --- a/examples/simple-examples/src/numbers/available/list.ts +++ b/examples/simple-examples/src/numbers/available/list.ts @@ -9,6 +9,7 @@ import { ListAvailableNumbersRequestData } from '@sinch/sdk-core'; const requestData: ListAvailableNumbersRequestData= { regionCode: 'US', type: 'LOCAL', + capabilities: ['SMS', 'VOICE'], }; const sinchClient = initClient(); diff --git a/packages/numbers/src/rest/v1/active-number/active-number-api.ts b/packages/numbers/src/rest/v1/active-number/active-number-api.ts index 92dc1aca..78c1164b 100644 --- a/packages/numbers/src/rest/v1/active-number/active-number-api.ts +++ b/packages/numbers/src/rest/v1/active-number/active-number-api.ts @@ -30,8 +30,8 @@ export interface ListActiveNumbersRequestData { 'numberPattern.pattern'?: string; /** Search pattern to apply. The options are, `START`, `CONTAIN`, and `END`. */ 'numberPattern.searchPattern'?: SearchPatternEnum; - /** Number capabilities to filter by, `SMS` and/or `VOICE`. */ - capability?: Array; + /** Number capabilities to filter by, `SMS` or `VOICE`. */ + capability?: CapabilitiesEnum; /** The maximum number of items to return. */ pageSize?: number; /** The next page token value returned from a previous List request, if any. */ @@ -140,8 +140,7 @@ export class ActiveNumberApi extends NumbersApi { this.client, requestOptionsPromise, operationProperties, - false, - ';'); + ); // Add properties to the Promise to offer the possibility to use it as an iterator Object.assign( diff --git a/packages/numbers/src/rest/v1/available-number/available-number-api.ts b/packages/numbers/src/rest/v1/available-number/available-number-api.ts index bc7d48a0..ffd383b1 100644 --- a/packages/numbers/src/rest/v1/available-number/available-number-api.ts +++ b/packages/numbers/src/rest/v1/available-number/available-number-api.ts @@ -122,7 +122,7 @@ export class AvailableNumberApi extends NumbersApi { headers, body || undefined, ); - const url = this.client.prepareUrl(requestOptions.basePath, requestOptions.queryParams, false, ';'); + const url = this.client.prepareUrl(requestOptions.basePath, requestOptions.queryParams, true); return this.client.processCall({ url, diff --git a/packages/sdk-client/src/api/api-client.ts b/packages/sdk-client/src/api/api-client.ts index c0f219ba..cef60263 100644 --- a/packages/sdk-client/src/api/api-client.ts +++ b/packages/sdk-client/src/api/api-client.ts @@ -75,9 +75,9 @@ export class ApiClient { .reduce( (acc, name) => { const prop = data[name]; - acc[name] = typeof prop.toJSON === 'function' - ? prop.toJSON() - : Array.isArray(prop) ? prop.join(',') : prop.toString(); + acc[name] = typeof prop.toJSON === 'object' + ? JSON.stringify(prop.toJSON()) + : JSON.stringify(prop); return acc; }, {} as { [p in keyof T]: string }, @@ -128,18 +128,16 @@ export class ApiClient { * @param {string} url - The base url to be used. * @param {Object.} queryParameters - Key-value pair with the parameters. If the value is undefined, the key is dropped. * @param {boolean} repeatParamArray - create as many single parameters with each value of the array - * @param {string} unexplodedSeparator - the separator to use between values when `explode` is false (default is ',') * @return {string} The prepared URL as a string. */ prepareUrl( url: string, queryParameters: { [key: string]: string | undefined } = {}, repeatParamArray?: boolean, - unexplodedSeparator?: string, ): string { const queryPart = Object.keys(queryParameters) .filter((name) => typeof queryParameters[name] !== 'undefined') - .map((name) => this.formatQueryParameter(name, queryParameters, repeatParamArray, unexplodedSeparator)) + .map((name) => this.formatQueryParameter(name, queryParameters, repeatParamArray)) .join('&'); const paramsPrefix = url.indexOf('?') > -1 ? '&' : '?'; @@ -152,26 +150,23 @@ export class ApiClient { * @param {string} name - The parameter name * @param {Object.} queryParameters - Key-value pair with the parameters. If the value is undefined, the key is dropped. * @param {boolean}repeatParamArray - Create as many single parameters with each value of the array - * @param {string} unexplodedSeparator - the separator to use between values when `explode` is false (default is `,`) * @return {string} The query parameter formatted as required by the API */ private formatQueryParameter = ( name: string, queryParameters: { [key: string]: string | undefined } = {}, repeatParamArray?: boolean, - unexplodedSeparator?: string, ): string => { - const defaultFormat = `${name}=${queryParameters[name]!}`; - if(repeatParamArray) { - const parameterValue = queryParameters[name]; - if (parameterValue && parameterValue.indexOf(',') > 0) { - const parameterValues = parameterValue.split(','); - return parameterValues.map((value) => `${name}=${value}`).join('&'); + const parameterValue = JSON.parse(queryParameters[name] || ''); + if (Array.isArray(parameterValue)) { + if (repeatParamArray) { + return parameterValue.map((value: string) => `${name}=${value}`).join('&'); + } else { + return `${name}=${parameterValue.join(',')}`; } - } else if (unexplodedSeparator !== undefined) { - return defaultFormat.replaceAll(',', unexplodedSeparator); + } else { + return `${name}=${parameterValue}`; } - return defaultFormat; }; /** diff --git a/packages/sdk-client/src/client/api-client-pagination-helper.ts b/packages/sdk-client/src/client/api-client-pagination-helper.ts index d101523f..adfbdd82 100644 --- a/packages/sdk-client/src/client/api-client-pagination-helper.ts +++ b/packages/sdk-client/src/client/api-client-pagination-helper.ts @@ -207,12 +207,11 @@ export const buildPageResultPromise = async ( requestOptionsPromise: Promise, operationProperties: PaginatedApiProperties, repeatParamArray?: boolean, - unexplodedSeparator?: string, ): Promise> => { // Await the promise in this async method and store the result in client so that they can be reused const requestOptions = await requestOptionsPromise; const url = client.prepareUrl( - requestOptions.basePath, requestOptions.queryParams, repeatParamArray, unexplodedSeparator); + requestOptions.basePath, requestOptions.queryParams, repeatParamArray); return client.processCallWithPagination({ url, diff --git a/packages/sdk-client/tests/api/api-client.test.ts b/packages/sdk-client/tests/api/api-client.test.ts index 4f495be4..93809e5d 100644 --- a/packages/sdk-client/tests/api/api-client.test.ts +++ b/packages/sdk-client/tests/api/api-client.test.ts @@ -13,44 +13,33 @@ describe('API client', () => { it('should format the URL with simple parameters', () => { const url = 'https://example.com'; - const parameters = { + const parameters = apiClient.extractQueryParams({ foo: 'fooValue', bar: '1', baz: undefined, - }; + }, ['foo', 'bar', 'baz'] ); const formattedUrl = apiClient.prepareUrl(url, parameters); expect(formattedUrl).toBe('https://example.com?foo=fooValue&bar=1'); }); it('should format the URL with array parameters', () => { const url = 'https://example.com'; - const parameters = { + const parameters = apiClient.extractQueryParams({ foo: 'fooValue', - bar: '1,2', + bar: ['1' ,'2'], baz: undefined, - }; + }, ['foo', 'bar', 'baz'] ); const formattedUrl = apiClient.prepareUrl(url, parameters); expect(formattedUrl).toBe('https://example.com?foo=fooValue&bar=1,2'); }); - it('should format the URL with array parameters and a custom separator', () => { - const url = 'https://example.com'; - const parameters = { - foo: 'fooValue', - bar: '1,2', - baz: undefined, - }; - const formattedUrl = apiClient.prepareUrl(url, parameters, false, ';'); - expect(formattedUrl).toBe('https://example.com?foo=fooValue&bar=1;2'); - }); - it('should format the URL with array parameters with repeat key', () => { const url = 'https://example.com'; - const parameters = { + const parameters = apiClient.extractQueryParams({ foo: 'fooValue', - bar: '1,2', + bar: ['1' ,'2'], baz: undefined, - }; + }, ['foo', 'bar', 'baz'] ); const formattedUrl = apiClient.prepareUrl(url, parameters, true); expect(formattedUrl).toBe('https://example.com?foo=fooValue&bar=1&bar=2'); });