Skip to content

Commit 5a3d24b

Browse files
committed
fix: sdk exploded syntax - take into account request plugin options and
make serialization optional
1 parent c894748 commit 5a3d24b

File tree

23 files changed

+609
-153
lines changed

23 files changed

+609
-153
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ const DEFAULT_OPTIONS = {
5555
angularPlugins: [],
5656
requestPlugins: [],
5757
enableTokenization: false,
58-
disableFallback: false
58+
disableFallback: false,
59+
enableParameterSerialization: false
5960
} as const satisfies Omit<BaseApiAngularClientOptions, 'basePath' | 'httpClient'>;
6061

6162
/** Client to process the call to the API using Angular API */
@@ -130,7 +131,7 @@ export class ApiAngularClient implements ApiClient {
130131
}
131132

132133
/** @inheritdoc */
133-
public prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
134+
public prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string {
134135
return prepareUrlWithQueryParams(url, serializedQueryParams);
135136
}
136137

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export interface BaseApiBeaconClientConstructor extends PartialExcept<Omit<BaseA
3535
const DEFAULT_OPTIONS = {
3636
replyPlugins: [] as never[],
3737
requestPlugins: [],
38-
enableTokenization: false
38+
enableTokenization: false,
39+
enableParameterSerialization: false
3940
} as const satisfies Omit<BaseApiBeaconClientOptions, 'basePath'>;
4041

4142
/**
@@ -130,7 +131,7 @@ export class ApiBeaconClient implements ApiClient {
130131
}
131132

132133
/** @inheritdoc */
133-
public prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
134+
public prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string {
134135
return prepareUrlWithQueryParams(url, serializedQueryParams);
135136
}
136137

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ const DEFAULT_OPTIONS = {
5151
fetchPlugins: [],
5252
requestPlugins: [],
5353
enableTokenization: false,
54-
disableFallback: false
54+
disableFallback: false,
55+
enableParameterSerialization: false
5556
} as const satisfies Omit<BaseApiFetchClientOptions, 'basePath'>;
5657

5758
/** Client to process the call to the API using Fetch API */
@@ -131,7 +132,7 @@ export class ApiFetchClient implements ApiClient {
131132
}
132133

133134
/** @inheritdoc */
134-
public prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
135+
public prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string {
135136
return prepareUrlWithQueryParams(url, serializedQueryParams);
136137
}
137138

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ const DEFAULT_OPTIONS: Omit<BaseApiAngularClientOptions, 'basePath' | 'httpClien
8282
angularPlugins: [],
8383
requestPlugins: [],
8484
enableTokenization: false,
85-
disableFallback: false
85+
disableFallback: false,
86+
enableParameterSerialization: false
8687
};
8788

8889
/**
@@ -160,7 +161,7 @@ export class ApiAngularClient implements ApiClient {
160161
}
161162

162163
/** @inheritdoc */
163-
public prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
164+
public prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string {
164165
return prepareUrlWithQueryParams(url, serializedQueryParams);
165166
}
166167

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ export interface BaseApiBeaconClientConstructor extends PartialExcept<Omit<BaseA
5252
const DEFAULT_OPTIONS: Omit<BaseApiBeaconClientOptions, 'basePath'> = {
5353
replyPlugins: [] as never[],
5454
requestPlugins: [],
55-
enableTokenization: false
55+
enableTokenization: false,
56+
enableParameterSerialization: false
5657
};
5758

5859
/**
@@ -148,7 +149,7 @@ export class ApiBeaconClient implements ApiClient {
148149
}
149150

150151
/** @inheritdoc */
151-
public prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
152+
public prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string {
152153
return prepareUrlWithQueryParams(url, serializedQueryParams);
153154
}
154155

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ const DEFAULT_OPTIONS: Omit<BaseApiFetchClientOptions, 'basePath'> = {
7373
fetchPlugins: [],
7474
requestPlugins: [],
7575
enableTokenization: false,
76-
disableFallback: false
76+
disableFallback: false,
77+
enableParameterSerialization: false
7778
};
7879

7980
/**
@@ -156,7 +157,7 @@ export class ApiFetchClient implements ApiClient {
156157
}
157158

158159
/** @inheritdoc */
159-
public prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
160+
public prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string {
160161
return prepareUrlWithQueryParams(url, serializedQueryParams);
161162
}
162163

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import type {
1313
* Prepares the url to be called
1414
* @param url Base url to be used
1515
* @param queryParameters Key value pair with the parameters. If the value is undefined, the key is dropped
16-
* @deprecated use {@link prepareUrlWithQueryParams} with query parameter serialization, will be removed in v14.
1716
*/
1817
export function prepareUrl(url: string, queryParameters: { [key: string]: string | undefined } = {}) {
1918
const queryPart = Object.keys(queryParameters)
@@ -31,7 +30,7 @@ export function prepareUrl(url: string, queryParameters: { [key: string]: string
3130
* @param url Base url to be used
3231
* @param serializedQueryParams Key value pairs of query parameter names and their serialized values
3332
*/
34-
export function prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string {
33+
export function prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string } = {}): string {
3534
const paramsPrefix = url.includes('?') ? '&' : '?';
3635
const queryPart = Object.values(serializedQueryParams).join('&');
3736
return url + (queryPart ? paramsPrefix + queryPart : '');

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
ParamSerializationOptions,
23
RequestBody,
34
RequestMetadata,
45
RequestOptions,
@@ -27,6 +28,8 @@ export interface RequestOptionsParameters {
2728
basePath: string;
2829
/** Query Parameters */
2930
queryParams?: { [key: string]: string | undefined };
31+
/** Parameter serialization options */
32+
paramSerializationOptions?: ParamSerializationOptions;
3033
/** Force body to string */
3134
body?: RequestBody;
3235
/** Force headers to Headers type */
@@ -91,7 +94,7 @@ export interface ApiClient {
9194
* @param url Base url to be used
9295
* @param serializedQueryParams Key value pairs of query parameter names and their serialized values
9396
*/
94-
prepareUrlWithQueryParams(url: string, serializedQueryParams: { [key: string]: string }): string;
97+
prepareUrlWithQueryParams(url: string, serializedQueryParams?: { [key: string]: string }): string;
9598

9699
/**
97100
* Serialize query parameters based on the values of exploded and style

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export interface BaseApiClientOptions {
2727
disableFallback?: boolean;
2828
/** Logger (optional, fallback to console logger if undefined) */
2929
logger?: Logger;
30+
/** Enable parameter serialization with exploded syntax */
31+
enableParameterSerialization?: boolean;
3032
/** Custom query parameter serialization method */
3133
serializeQueryParams?<T extends { [key: string]: SupportedParamType }>(queryParams: T, queryParamSerialization: { [p in keyof T]: ParamSerialization }): { [p in keyof T]: string };
3234
/** Custom query parameter serialization method */

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

Lines changed: 44 additions & 10 deletions
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,31 @@ 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]: QueryParamValueSerialization }) {
51+
const queryParamsValues: { [key: string]: SupportedParamType } = {};
52+
const queryParamSerialization: { [key: string]: ParamSerialization } = {};
53+
Object.entries(queryParams).forEach(([paramKey, paramValue]) => {
54+
queryParamsValues[paramKey] = paramValue.value;
55+
queryParamSerialization[paramKey] = { explode: paramValue.explode, style: paramValue.style };
56+
});
57+
return serializeQueryParams(queryParamsValues, queryParamSerialization);
58+
}
59+
2660
/**
2761
* Serialize query parameters of type array
2862
* OpenAPI Parameter Serialization {@link https://swagger.io/specification | documentation}
@@ -46,13 +80,13 @@ function serializeArrayQueryParams(queryParamName: string, queryParamValue: Prim
4680
if (emptyArray) {
4781
break;
4882
}
49-
return encodeURIComponent(queryParamName) + '=' + filteredArray.map((v) => isDateType(v) ? v.toJSON() : encodeURIComponent(v.toString())).join('%20');
83+
return encodeURIComponent(queryParamName) + '=' + filteredArray.map((v) => isDateType(v) ? v.toJSON() : encodeURIComponent(v.toString())).join(SPACE_URL_CODE);
5084
}
5185
case 'pipeDelimited': {
5286
if (emptyArray) {
5387
break;
5488
}
55-
return encodeURIComponent(queryParamName) + '=' + filteredArray.map((v) => isDateType(v) ? v.toJSON() : encodeURIComponent(v.toString())).join('%7C');
89+
return encodeURIComponent(queryParamName) + '=' + filteredArray.map((v) => isDateType(v) ? v.toJSON() : encodeURIComponent(v.toString())).join(PIPE_URL_CODE);
5690
}
5791
}
5892
}
@@ -81,15 +115,16 @@ function serializeObjectQueryParams(queryParamName: string, queryParamValue: { [
81115
encodeURIComponent(propName) + ',' + (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))).join(',');
82116
} else if (paramSerialization.style === 'spaceDelimited' && !paramSerialization.explode && !emptyObject) {
83117
return encodeURIComponent(queryParamName) + '=' + Object.entries(filteredObject).map(([propName, propValue]) =>
84-
encodeURIComponent(propName) + '%20' + (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))
85-
).join('%20');
118+
encodeURIComponent(propName) + SPACE_URL_CODE + (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))
119+
).join(SPACE_URL_CODE);
86120
} else if (paramSerialization.style === 'pipeDelimited' && !paramSerialization.explode && !emptyObject) {
87121
return encodeURIComponent(queryParamName) + '=' + Object.entries(filteredObject).map(([propName, propValue]) =>
88-
encodeURIComponent(propName) + '%7C' + (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))
89-
).join('%7C');
122+
encodeURIComponent(propName) + PIPE_URL_CODE + (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))
123+
).join(PIPE_URL_CODE);
90124
} else if (paramSerialization.style === 'deepObject' && paramSerialization.explode && !emptyObject) {
91125
return Object.entries(filteredObject).map(([propName, propValue]) =>
92-
encodeURIComponent(queryParamName) + '%5B' + encodeURIComponent(propName) + '%5D=' + (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))
126+
encodeURIComponent(queryParamName) + OPENING_SQUARE_BRACKET_URL_CODE + encodeURIComponent(propName) + CLOSING_SQUARE_BRACKET_URL_CODE + '='
127+
+ (isDateType(propValue) ? propValue.toJSON() : encodeURIComponent(propValue.toString()))
93128
).join('&');
94129
}
95130
}
@@ -110,9 +145,8 @@ export function serializeQueryParams<T extends { [key: string]: SupportedParamTy
110145
} else if (typeof queryParamValue === 'object' && !isDateType(queryParamValue)) {
111146
serializedValue = serializeObjectQueryParams(queryParamName, queryParamValue, paramSerialization);
112147
} else {
113-
if (paramSerialization.style === 'form') {
114-
serializedValue = encodeURIComponent(queryParamName) + '=' + (isDateType(queryParamValue) ? queryParamValue.toJSON() : encodeURIComponent(queryParamValue.toString()));
115-
}
148+
// NOTE: 'form' style is the default value for primitive types
149+
serializedValue = encodeURIComponent(queryParamName) + '=' + (isDateType(queryParamValue) ? queryParamValue.toJSON() : encodeURIComponent(queryParamValue.toString()));
116150
}
117151
if (serializedValue) {
118152
acc[queryParamName as keyof T] = serializedValue;

0 commit comments

Comments
 (0)