Skip to content

Commit baa7fc6

Browse files
committed
fix: sdk exploded syntax - take into account request plugin options
1 parent 0a1fa58 commit baa7fc6

File tree

22 files changed

+211
-86
lines changed

22 files changed

+211
-86
lines changed

packages/@ama-sdk/client-angular/src/api-angular-client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export class ApiAngularClient implements ApiClient {
130130
}
131131

132132
/** @inheritdoc */
133-
public prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
133+
public prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string {
134134
return prepareUrlWithQueryParams(url, serializedQueryParams);
135135
}
136136

packages/@ama-sdk/client-beacon/src/api-beacon-client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export class ApiBeaconClient implements ApiClient {
130130
}
131131

132132
/** @inheritdoc */
133-
public prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
133+
public prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string {
134134
return prepareUrlWithQueryParams(url, serializedQueryParams);
135135
}
136136

packages/@ama-sdk/client-fetch/src/api-fetch-client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class ApiFetchClient implements ApiClient {
131131
}
132132

133133
/** @inheritdoc */
134-
public prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
134+
public prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string {
135135
return prepareUrlWithQueryParams(url, serializedQueryParams);
136136
}
137137

packages/@ama-sdk/core/src/clients/api-angular-client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ export class ApiAngularClient implements ApiClient {
160160
}
161161

162162
/** @inheritdoc */
163-
public prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
163+
public prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string {
164164
return prepareUrlWithQueryParams(url, serializedQueryParams);
165165
}
166166

packages/@ama-sdk/core/src/clients/api-beacon-client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ export class ApiBeaconClient implements ApiClient {
148148
}
149149

150150
/** @inheritdoc */
151-
public prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
151+
public prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string {
152152
return prepareUrlWithQueryParams(url, serializedQueryParams);
153153
}
154154

packages/@ama-sdk/core/src/clients/api-fetch-client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export class ApiFetchClient implements ApiClient {
156156
}
157157

158158
/** @inheritdoc */
159-
public prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
159+
public prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string {
160160
return prepareUrlWithQueryParams(url, serializedQueryParams);
161161
}
162162

packages/@ama-sdk/core/src/fwk/api.helpers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function prepareUrl(url: string, queryParameters: { [key: string]: string
3131
* @param url Base url to be used
3232
* @param serializedQueryParams Key value pairs of query parameter names and their serialized values
3333
*/
34-
export function prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
34+
export function prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string } = {}): string {
3535
const paramsPrefix = url.includes('?') ? '&' : '?';
3636
const queryPart = Object.values(serializedQueryParams).join('&');
3737
return url + (queryPart ? paramsPrefix + queryPart : '');

packages/@ama-sdk/core/src/fwk/core/api-client.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export interface RequestOptionsParameters {
2727
basePath: string;
2828
/** Query Parameters */
2929
queryParams?: { [key: string]: string | undefined };
30+
/** Serialization of the query parameters (style and explode) */
31+
queryParamSerialization?: { [key: string]: ParamSerialization };
3032
/** Force body to string */
3133
body?: RequestBody;
3234
/** Force headers to Headers type */
@@ -91,7 +93,7 @@ export interface ApiClient {
9193
* @param url Base url to be used
9294
* @param serializedQueryParams Key value pairs of query parameter names and their serialized values
9395
*/
94-
prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string;
96+
prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string;
9597

9698
/**
9799
* Serialize query parameters based on the values of exploded and style

packages/@ama-sdk/core/src/fwk/param-serialization.ts

+51-10
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ export type PrimitiveType = string | number | boolean | Date | utils.Date | util
1515
/** Supported types for the operation parameters - primitives, primitive arrays, and simple non-nested objects */
1616
export type SupportedParamType = PrimitiveType | PrimitiveType[] | { [key: string]: PrimitiveType };
1717

18+
/** URL encoding of space character, delimiter for spaceDelimited style */
19+
const SPACE_URL_CODE = encodeURIComponent(' ');
20+
/** URL encoding of pipe character, delimiter for pipeDelimited style */
21+
const PIPE_URL_CODE = encodeURIComponent('|');
22+
/** URL encoding of opening square bracket, used in deepObject style */
23+
const OPENING_SQUARE_BRACKET_URL_CODE = encodeURIComponent('[');
24+
/** URL encoding of closing square bracket, used in deepObject style */
25+
const CLOSING_SQUARE_BRACKET_URL_CODE = encodeURIComponent(']');
26+
1827
/**
1928
* Verify if property is of type utils.Date or utils.DateTime
2029
* @param prop
@@ -23,6 +32,38 @@ export function isDateType(prop: any): prop is Date | utils.Date | utils.DateTim
2332
return prop instanceof Date || prop instanceof utils.Date || prop instanceof utils.DateTime;
2433
}
2534

35+
/**
36+
* Check if the parameter is a record of type <string, string>.
37+
* @param param
38+
*/
39+
export function isParamValueRecord(param: any): param is { [key: string]: string } {
40+
return typeof param === 'object' && Object.values(param).every((item) => typeof item === 'string');
41+
}
42+
43+
/** Query parameter value and serialization */
44+
export type QueryParamValueSerialization = { value: SupportedParamType } & ParamSerialization;
45+
46+
/**
47+
* Serialize query parameters of request plugins
48+
* @param queryParams
49+
*/
50+
export function serializeRequestPluginQueryParams(queryParams: { [key: string]: string } | { [key: string]: QueryParamValueSerialization }) {
51+
// NOTE: Check if the type of the property values of `queryParams` is string
52+
if (isParamValueRecord(queryParams)) {
53+
return Object.entries(queryParams).reduce((acc, [paramKey, paramValue]) => {
54+
acc[paramKey] = `${paramKey}=${paramValue}`;
55+
return acc;
56+
}, {} as { [key: string]: string });
57+
}
58+
const queryParamsValues: { [key: string]: SupportedParamType } = {};
59+
const queryParamSerialization: { [key: string]: ParamSerialization } = {};
60+
Object.entries(queryParams).forEach(([paramKey, paramValue]) => {
61+
queryParamsValues[paramKey] = paramValue.value;
62+
queryParamSerialization[paramKey] = { explode: paramValue.explode, style: paramValue.style };
63+
});
64+
return serializeQueryParams(queryParamsValues, queryParamSerialization);
65+
}
66+
2667
/**
2768
* Serialize query parameters of type array
2869
* OpenAPI Parameter Serialization {@link https://swagger.io/specification | documentation}
@@ -46,13 +87,13 @@ function serializeArrayQueryParams(queryParamName: string, queryParamValue: Prim
4687
if (emptyArray) {
4788
break;
4889
}
49-
return encodeURIComponent(queryParamName) + '=' + filteredArray.map((v) => isDateType(v) ? v.toJSON() : encodeURIComponent(v.toString())).join('%20');
90+
return encodeURIComponent(queryParamName) + '=' + filteredArray.map((v) => isDateType(v) ? v.toJSON() : encodeURIComponent(v.toString())).join(SPACE_URL_CODE);
5091
}
5192
case 'pipeDelimited': {
5293
if (emptyArray) {
5394
break;
5495
}
55-
return encodeURIComponent(queryParamName) + '=' + filteredArray.map((v) => isDateType(v) ? v.toJSON() : encodeURIComponent(v.toString())).join('%7C');
96+
return encodeURIComponent(queryParamName) + '=' + filteredArray.map((v) => isDateType(v) ? v.toJSON() : encodeURIComponent(v.toString())).join(PIPE_URL_CODE);
5697
}
5798
}
5899
}
@@ -81,15 +122,16 @@ function serializeObjectQueryParams(queryParamName: string, queryParamValue: { [
81122
encodeURIComponent(propName) + ',' + (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))).join(',');
82123
} else if (paramSerialization.style === 'spaceDelimited' && !paramSerialization.explode && !emptyObject) {
83124
return encodeURIComponent(queryParamName) + '=' + Object.entries(filteredObject).map(([propName, propValue]) =>
84-
encodeURIComponent(propName) + '%20' + (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))
85-
).join('%20');
125+
encodeURIComponent(propName) + SPACE_URL_CODE + (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))
126+
).join(SPACE_URL_CODE);
86127
} else if (paramSerialization.style === 'pipeDelimited' && !paramSerialization.explode && !emptyObject) {
87128
return encodeURIComponent(queryParamName) + '=' + Object.entries(filteredObject).map(([propName, propValue]) =>
88-
encodeURIComponent(propName) + '%7C' + (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))
89-
).join('%7C');
129+
encodeURIComponent(propName) + PIPE_URL_CODE + (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))
130+
).join(PIPE_URL_CODE);
90131
} else if (paramSerialization.style === 'deepObject' && paramSerialization.explode && !emptyObject) {
91132
return Object.entries(filteredObject).map(([propName, propValue]) =>
92-
encodeURIComponent(queryParamName) + '%5B' + encodeURIComponent(propName) + '%5D=' + (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))
133+
encodeURIComponent(queryParamName) + OPENING_SQUARE_BRACKET_URL_CODE + encodeURIComponent(propName) + CLOSING_SQUARE_BRACKET_URL_CODE + '='
134+
+ (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))
93135
).join('&');
94136
}
95137
}
@@ -110,9 +152,8 @@ export function serializeQueryParams<T extends { [key: string]: SupportedParamTy
110152
} else if (typeof queryParamValue === 'object' && !isDateType(queryParamValue)) {
111153
serializedValue = serializeObjectQueryParams(queryParamName, queryParamValue, paramSerialization);
112154
} else {
113-
if (paramSerialization.style === 'form') {
114-
serializedValue = encodeURIComponent(queryParamName) + '=' + (isDateType(queryParamValue) ? queryParamValue.toJSON() : encodeURIComponent(queryParamValue.toString()));
115-
}
155+
// NOTE: 'form' style is the default value for primitive types
156+
serializedValue = encodeURIComponent(queryParamName) + '=' + (isDateType(queryParamValue) ? queryParamValue.toJSON() : encodeURIComponent(queryParamValue.toString()));
116157
}
117158
if (serializedValue) {
118159
acc[queryParamName as keyof T] = serializedValue;

packages/@ama-sdk/core/src/plugins/additional-params/additional-params-sync.request.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
type QueryParamValueSerialization,
3+
serializeRequestPluginQueryParams,
4+
} from '../../fwk/param-serialization';
15
import {
26
PluginSyncRunner,
37
RequestOptions,
@@ -11,7 +15,8 @@ export interface AdditionalParametersSync {
1115
/** Additional headers */
1216
headers?: { [key: string]: string } | ((headers: Headers) => { [key: string]: string });
1317
/** Additional query params */
14-
queryParams?: { [key: string]: string } | ((defaultValues?: { [key: string]: string }) => { [key: string]: string });
18+
queryParams?: { [key: string]: string } | { [key: string]: QueryParamValueSerialization }
19+
| ((defaultValues?: { [key: string]: string } | { [key: string]: QueryParamValueSerialization }) => { [key: string]: string } | { [key: string]: QueryParamValueSerialization });
1520
/** Additional body params */
1621
body?: (defaultValues?: string) => string | null;
1722
}
@@ -38,7 +43,7 @@ export class AdditionalParamsSyncRequest implements RequestPlugin {
3843
const body = this.additionalParams.body && isStringOrUndefined(data.body) ? this.additionalParams.body(data.body) : undefined;
3944

4045
if (queryParams) {
41-
data.queryParams = { ...data.queryParams, ...queryParams };
46+
data.queryParams = { ...data.queryParams, ...serializeRequestPluginQueryParams(queryParams) };
4247
}
4348

4449
if (body !== undefined) {

packages/@ama-sdk/core/src/plugins/additional-params/additional-params.request.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import {
2+
type QueryParamValueSerialization,
3+
serializeRequestPluginQueryParams,
4+
} from '../../fwk/param-serialization';
15
import {
26
PluginRunner,
37
RequestOptions,
@@ -8,7 +12,10 @@ export interface AdditionalParameters {
812
/** Additional headers */
913
headers?: { [key: string]: string } | ((headers: Headers) => { [key: string]: string } | Promise<{ [key: string]: string }>);
1014
/** Additional query params */
11-
queryParams?: { [key: string]: string } | ((defaultValues?: { [key: string]: string }) => { [key: string]: string } | Promise<{ [key: string]: string }>);
15+
queryParams?: { [key: string]: string } | { [key: string]: QueryParamValueSerialization }
16+
| ((defaultValues?: { [key: string]: string } | { [key: string]: QueryParamValueSerialization }) =>
17+
{ [key: string]: string } | { [key: string]: QueryParamValueSerialization } | Promise<{ [key: string]: string }> | Promise<{ [key: string]: QueryParamValueSerialization }>
18+
);
1219
/** Additional body params */
1320
body?: (defaultValues?: string) => string | null | Promise<string>;
1421
}
@@ -45,7 +52,7 @@ export class AdditionalParamsRequest implements RequestPlugin {
4552
const body = this.additionalParams.body && isStringOrUndefined(data.body) ? this.additionalParams.body(data.body) : undefined;
4653

4754
if (queryParams) {
48-
data.queryParams = { ...data.queryParams, ...queryParams };
55+
data.queryParams = { ...data.queryParams, ...serializeRequestPluginQueryParams(queryParams) };
4956
}
5057

5158
if (body !== undefined) {

packages/@ama-sdk/core/src/plugins/additional-params/additional-params.spec.ts

+48-8
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe('Additional Params Request Plugin', () => {
1212
const additionalGetParams = jest.fn().mockReturnValue({ test: 'ok' });
1313
const additionalBody = jest.fn().mockReturnValue('newBody');
1414

15-
const defaultGetParams = { defaultTest: 'ok' };
15+
const defaultGetParams = { defaultTest: 'defaultTest=ok' };
1616
const defaultBody = 'default';
1717
let options: RequestOptions;
1818

@@ -32,7 +32,27 @@ describe('Additional Params Request Plugin', () => {
3232

3333
const result = await runner.transform(options);
3434

35-
expect(result.queryParams.test).toBe('ok');
35+
expect(result.queryParams.test).toBe('test=ok');
36+
});
37+
38+
it('should add serialized query params', async () => {
39+
const plugin = new AdditionalParamsRequest({ queryParams:
40+
{
41+
primParam: { value: 'ok', explode: false, style: 'form' },
42+
arrParam: { value: ['a', 'b', 'c'], explode: false, style: 'spaceDelimited' },
43+
objParam: { value: { prop1: 'value1', prop2: 'value2' }, explode: true, style: 'deepObject' }
44+
}
45+
});
46+
const runner = plugin.load();
47+
48+
const result = await runner.transform(options);
49+
50+
expect(result.queryParams).toStrictEqual({
51+
defaultTest: 'defaultTest=ok',
52+
primParam: 'primParam=ok',
53+
arrParam: 'arrParam=a%20b%20c',
54+
objParam: 'objParam%5Bprop1%5D=value1&objParam%5Bprop2%5D=value2'
55+
});
3656
});
3757

3858
it('should add the query params returned by a function', async () => {
@@ -42,7 +62,7 @@ describe('Additional Params Request Plugin', () => {
4262
const result = await runner.transform(options);
4363

4464
expect(additionalGetParams).toHaveBeenCalledWith(defaultGetParams);
45-
expect(result.queryParams.test).toBe('ok');
65+
expect(result.queryParams.test).toBe('test=ok');
4666
});
4767

4868
it('should modify body', async () => {
@@ -52,7 +72,7 @@ describe('Additional Params Request Plugin', () => {
5272
const result = await runner.transform(options);
5373

5474
expect(result.queryParams.test).toBeUndefined();
55-
expect(result.queryParams.defaultTest).toBe('ok');
75+
expect(result.queryParams.defaultTest).toBe('defaultTest=ok');
5676

5777
expect(additionalBody).toHaveBeenCalledWith(defaultBody);
5878
expect(result.body).toBe('newBody');
@@ -63,7 +83,7 @@ describe('Additional Params Request Sync Plugin', () => {
6383
const additionalGetParams = jest.fn().mockReturnValue({ test: 'ok' });
6484
const additionalBody = jest.fn().mockReturnValue('newBody');
6585

66-
const defaultGetParams = { defaultTest: 'ok' };
86+
const defaultGetParams = { defaultTest: 'defaultTest=ok' };
6787
const defaultBody = 'default';
6888
let options: RequestOptions;
6989

@@ -83,7 +103,27 @@ describe('Additional Params Request Sync Plugin', () => {
83103

84104
const result = runner.transform(options);
85105

86-
expect(result.queryParams.test).toBe('ok');
106+
expect(result.queryParams.test).toBe('test=ok');
107+
});
108+
109+
it('should add serialized query params', () => {
110+
const plugin = new AdditionalParamsSyncRequest({ queryParams:
111+
{
112+
primParam: { value: 'ok', explode: false, style: 'form' },
113+
arrParam: { value: ['a', 'b', 'c'], explode: false, style: 'spaceDelimited' },
114+
objParam: { value: { prop1: 'value1', prop2: 'value2' }, explode: true, style: 'deepObject' }
115+
}
116+
});
117+
const runner = plugin.load();
118+
119+
const result = runner.transform(options);
120+
121+
expect(result.queryParams).toStrictEqual({
122+
defaultTest: 'defaultTest=ok',
123+
primParam: 'primParam=ok',
124+
arrParam: 'arrParam=a%20b%20c',
125+
objParam: 'objParam%5Bprop1%5D=value1&objParam%5Bprop2%5D=value2'
126+
});
87127
});
88128

89129
it('should add the query params returned by a function', () => {
@@ -93,7 +133,7 @@ describe('Additional Params Request Sync Plugin', () => {
93133
const result = runner.transform(options);
94134

95135
expect(additionalGetParams).toHaveBeenCalledWith(defaultGetParams);
96-
expect(result.queryParams.test).toBe('ok');
136+
expect(result.queryParams.test).toBe('test=ok');
97137
});
98138

99139
it('should modify body', () => {
@@ -103,7 +143,7 @@ describe('Additional Params Request Sync Plugin', () => {
103143
const result = runner.transform(options);
104144

105145
expect(result.queryParams.test).toBeUndefined();
106-
expect(result.queryParams.defaultTest).toBe('ok');
146+
expect(result.queryParams.defaultTest).toBe('defaultTest=ok');
107147

108148
expect(additionalBody).toHaveBeenCalledWith(defaultBody);
109149
expect(result.body).toBe('newBody');

packages/@ama-sdk/core/src/plugins/json-token/json-token.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('Json Token', () => {
1616
const tokenKey = 'testToken';
1717

1818
describe('request plugin', () => {
19-
const defaultGetParams = { defaultTest: 'ok' };
19+
const defaultGetParams = { defaultTest: 'defaultTest=ok' };
2020
const defaultBody = 'default';
2121
let options: RequestOptions;
2222

packages/@ama-sdk/core/src/plugins/si-token/si-token.request.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import type {
2+
QueryParamValueSerialization,
3+
} from '../../fwk/param-serialization';
14
import {
25
AdditionalParamsRequest,
36
} from '../additional-params';
@@ -24,7 +27,7 @@ export class SiTokenRequest extends AdditionalParamsRequest implements RequestPl
2427
*/
2528
constructor(siToken?: string, siToken2?: string) {
2629
super({
27-
queryParams: (queryParams?: { [key: string]: string }) => {
30+
queryParams: (queryParams?: { [key: string]: string } | { [key: string]: QueryParamValueSerialization }) => {
2831
const ret = queryParams || {};
2932
if (this.siToken) {
3033
ret.SITK = this.siToken;

packages/@ama-sdk/core/src/plugins/si-token/si-token.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
} from './si-token.request';
77

88
describe('SI Token Request Plugin', () => {
9-
const defaultGetParams = { defaultTest: 'ok' };
9+
const defaultGetParams = { defaultTest: 'defaultTest=ok' };
1010
const defaultBody = 'default';
1111
let options: RequestOptions;
1212

@@ -26,7 +26,7 @@ describe('SI Token Request Plugin', () => {
2626

2727
const result = await runner.transform(options);
2828

29-
expect(result.queryParams.SITK).toBe('SIToken1');
30-
expect(result.queryParams.SITK2).toBe('SIToken2');
29+
expect(result.queryParams.SITK).toBe('SITK=SIToken1');
30+
expect(result.queryParams.SITK2).toBe('SITK2=SIToken2');
3131
});
3232
});

0 commit comments

Comments
 (0)