Skip to content

Commit aea43f6

Browse files
committed
fix: sdk exploded syntax - make parameter serialization optional
1 parent baa7fc6 commit aea43f6

File tree

25 files changed

+441
-178
lines changed

25 files changed

+441
-178
lines changed

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

+2-1
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 */

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

+2-1
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
/**

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

+2-1
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 */

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

+2-1
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
/**

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

+2-1
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
/**

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

+2-1
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
/**

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

-1
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)

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
ParamSerializationOptions,
23
RequestBody,
34
RequestMetadata,
45
RequestOptions,
@@ -27,8 +28,8 @@ export interface RequestOptionsParameters {
2728
basePath: string;
2829
/** Query Parameters */
2930
queryParams?: { [key: string]: string | undefined };
30-
/** Serialization of the query parameters (style and explode) */
31-
queryParamSerialization?: { [key: string]: ParamSerialization };
31+
/** Parameter serialization options */
32+
paramSerializationOptions?: ParamSerializationOptions;
3233
/** Force body to string */
3334
body?: RequestBody;
3435
/** Force headers to Headers type */

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

+2
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

+1-8
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,7 @@ export type QueryParamValueSerialization = { value: SupportedParamType } & Param
4747
* Serialize query parameters of request plugins
4848
* @param queryParams
4949
*/
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-
}
50+
export function serializeRequestPluginQueryParams(queryParams: { [key: string]: QueryParamValueSerialization }) {
5851
const queryParamsValues: { [key: string]: SupportedParamType } = {};
5952
const queryParamSerialization: { [key: string]: ParamSerialization } = {};
6053
Object.entries(queryParams).forEach(([paramKey, paramValue]) => {

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
isParamValueRecord,
23
type QueryParamValueSerialization,
34
serializeRequestPluginQueryParams,
45
} from '../../fwk/param-serialization';
@@ -43,7 +44,11 @@ export class AdditionalParamsSyncRequest implements RequestPlugin {
4344
const body = this.additionalParams.body && isStringOrUndefined(data.body) ? this.additionalParams.body(data.body) : undefined;
4445

4546
if (queryParams) {
46-
data.queryParams = { ...data.queryParams, ...serializeRequestPluginQueryParams(queryParams) };
47+
if (data.paramSerializationOptions?.enableParameterSerialization && !isParamValueRecord(queryParams)) {
48+
data.queryParams = { ...data.queryParams, ...serializeRequestPluginQueryParams(queryParams) };
49+
} else if (isParamValueRecord(queryParams)) {
50+
data.queryParams = { ...data.queryParams, ...queryParams };
51+
}
4752
}
4853

4954
if (body !== undefined) {

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
isParamValueRecord,
23
type QueryParamValueSerialization,
34
serializeRequestPluginQueryParams,
45
} from '../../fwk/param-serialization';
@@ -52,7 +53,11 @@ export class AdditionalParamsRequest implements RequestPlugin {
5253
const body = this.additionalParams.body && isStringOrUndefined(data.body) ? this.additionalParams.body(data.body) : undefined;
5354

5455
if (queryParams) {
55-
data.queryParams = { ...data.queryParams, ...serializeRequestPluginQueryParams(queryParams) };
56+
if (data.paramSerializationOptions?.enableParameterSerialization && !isParamValueRecord(queryParams)) {
57+
data.queryParams = { ...data.queryParams, ...serializeRequestPluginQueryParams(queryParams) };
58+
} else if (isParamValueRecord(queryParams)) {
59+
data.queryParams = { ...data.queryParams, ...queryParams };
60+
}
5661
}
5762

5863
if (body !== undefined) {

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

+12-10
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: 'defaultTest=ok' };
15+
const defaultGetParams = { defaultTest: 'ok' };
1616
const defaultBody = 'default';
1717
let options: RequestOptions;
1818

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

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

35-
expect(result.queryParams.test).toBe('test=ok');
35+
expect(result.queryParams.test).toBe('ok');
3636
});
3737

3838
it('should add serialized query params', async () => {
39+
options.paramSerializationOptions = { enableParameterSerialization: true };
3940
const plugin = new AdditionalParamsRequest({ queryParams:
4041
{
4142
primParam: { value: 'ok', explode: false, style: 'form' },
@@ -48,7 +49,7 @@ describe('Additional Params Request Plugin', () => {
4849
const result = await runner.transform(options);
4950

5051
expect(result.queryParams).toStrictEqual({
51-
defaultTest: 'defaultTest=ok',
52+
defaultTest: 'ok',
5253
primParam: 'primParam=ok',
5354
arrParam: 'arrParam=a%20b%20c',
5455
objParam: 'objParam%5Bprop1%5D=value1&objParam%5Bprop2%5D=value2'
@@ -62,7 +63,7 @@ describe('Additional Params Request Plugin', () => {
6263
const result = await runner.transform(options);
6364

6465
expect(additionalGetParams).toHaveBeenCalledWith(defaultGetParams);
65-
expect(result.queryParams.test).toBe('test=ok');
66+
expect(result.queryParams.test).toBe('ok');
6667
});
6768

6869
it('should modify body', async () => {
@@ -72,7 +73,7 @@ describe('Additional Params Request Plugin', () => {
7273
const result = await runner.transform(options);
7374

7475
expect(result.queryParams.test).toBeUndefined();
75-
expect(result.queryParams.defaultTest).toBe('defaultTest=ok');
76+
expect(result.queryParams.defaultTest).toBe('ok');
7677

7778
expect(additionalBody).toHaveBeenCalledWith(defaultBody);
7879
expect(result.body).toBe('newBody');
@@ -83,7 +84,7 @@ describe('Additional Params Request Sync Plugin', () => {
8384
const additionalGetParams = jest.fn().mockReturnValue({ test: 'ok' });
8485
const additionalBody = jest.fn().mockReturnValue('newBody');
8586

86-
const defaultGetParams = { defaultTest: 'defaultTest=ok' };
87+
const defaultGetParams = { defaultTest: 'ok' };
8788
const defaultBody = 'default';
8889
let options: RequestOptions;
8990

@@ -103,10 +104,11 @@ describe('Additional Params Request Sync Plugin', () => {
103104

104105
const result = runner.transform(options);
105106

106-
expect(result.queryParams.test).toBe('test=ok');
107+
expect(result.queryParams.test).toBe('ok');
107108
});
108109

109110
it('should add serialized query params', () => {
111+
options.paramSerializationOptions = { enableParameterSerialization: true };
110112
const plugin = new AdditionalParamsSyncRequest({ queryParams:
111113
{
112114
primParam: { value: 'ok', explode: false, style: 'form' },
@@ -119,7 +121,7 @@ describe('Additional Params Request Sync Plugin', () => {
119121
const result = runner.transform(options);
120122

121123
expect(result.queryParams).toStrictEqual({
122-
defaultTest: 'defaultTest=ok',
124+
defaultTest: 'ok',
123125
primParam: 'primParam=ok',
124126
arrParam: 'arrParam=a%20b%20c',
125127
objParam: 'objParam%5Bprop1%5D=value1&objParam%5Bprop2%5D=value2'
@@ -133,7 +135,7 @@ describe('Additional Params Request Sync Plugin', () => {
133135
const result = runner.transform(options);
134136

135137
expect(additionalGetParams).toHaveBeenCalledWith(defaultGetParams);
136-
expect(result.queryParams.test).toBe('test=ok');
138+
expect(result.queryParams.test).toBe('ok');
137139
});
138140

139141
it('should modify body', () => {
@@ -143,7 +145,7 @@ describe('Additional Params Request Sync Plugin', () => {
143145
const result = runner.transform(options);
144146

145147
expect(result.queryParams.test).toBeUndefined();
146-
expect(result.queryParams.defaultTest).toBe('defaultTest=ok');
148+
expect(result.queryParams.defaultTest).toBe('ok');
147149

148150
expect(additionalBody).toHaveBeenCalledWith(defaultBody);
149151
expect(result.body).toBe('newBody');

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

+13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {
22
Api,
3+
ParamSerialization,
34
} from '../../fwk';
45
import type {
56
Plugin,
@@ -44,9 +45,21 @@ export interface RequestMetadata<C extends string = string, A extends string = s
4445
signal?: AbortSignal;
4546
}
4647

48+
/**
49+
* Options for the serialization of the parameters
50+
*/
51+
export interface ParamSerializationOptions {
52+
/** Serialization of the query parameters (style and explode) */
53+
queryParamSerialization?: { [key: string]: ParamSerialization };
54+
/** Enable parameter serialization with exploded syntax */
55+
enableParameterSerialization?: boolean;
56+
}
57+
4758
export interface RequestOptions extends RequestInit {
4859
/** Query Parameters */
4960
queryParams?: { [key: string]: string };
61+
/** Parameter serialization options */
62+
paramSerializationOptions?: ParamSerializationOptions;
5063
/** Force body to string */
5164
body?: RequestBody;
5265
/** Force headers to Headers type */

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: 'defaultTest=ok' };
19+
const defaultGetParams = { defaultTest: 'ok' };
2020
const defaultBody = 'default';
2121
let options: RequestOptions;
2222

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: 'defaultTest=ok' };
9+
const defaultGetParams = { 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('SITK=SIToken1');
30-
expect(result.queryParams.SITK2).toBe('SITK2=SIToken2');
29+
expect(result.queryParams.SITK).toBe('SIToken1');
30+
expect(result.queryParams.SITK2).toBe('SIToken2');
3131
});
3232
});

packages/@ama-sdk/core/src/plugins/url-rewrite/url-rewrite.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
describe('URL Rewrite Request Plugin', () => {
99
const urlRewriter = jest.fn().mockReturnValue('http://ok');
1010

11-
const defaultGetParams = { defaultTest: 'defaultTest=ok' };
11+
const defaultGetParams = { defaultTest: 'ok' };
1212
const defaultBody = 'default';
1313
const defaultUrl = 'http://test.com/truc';
1414
let options: RequestOptions;

packages/@ama-sdk/schematics/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ For more information, check out OpenAPI's documentation on [parameter serializat
119119
It is important to note that, as in OpenAPI 3.1, we only support simple arrays and simple non-nested objects in path and query parameters.
120120
The parameter types that we support are stored in `SupportedParamType` in the package `@ama-sdk/core`.
121121

122+
To enable the parameter serialization within your API, you can set the option `enableParameterSerialization` to `true` (its current default value is `false`) in the constructor. For example:
123+
```typescript
124+
const apiConfig: ApiClient = new ApiFetchClient(
125+
{
126+
basePath: 'https://petstore3.swagger.io/api/v3',
127+
enableParameterSerialization: true
128+
}
129+
);
130+
```
131+
122132
We provide the methods `serializeQueryParams` and `serializePathParams` to serialize the values of query and path parameters. However, it is also possible to pass
123133
your own serialization methods if the ones provided do not meet your requirements. These custom methods can be passed as a parameter to the API client constructor.
124134

packages/@ama-sdk/schematics/schematics/typescript/core/openapi-codegen-typescript/src/main/java/com/amadeus/codegen/ts/AbstractTypeScriptClientCodegen.java

+2
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@ public AbstractTypeScriptClientCodegen() {
146146
additionalProperties.put("parseRegexp", new LambdaHelper.ParseRegexp());
147147
additionalProperties.put("plurialize", new LambdaHelper.Plurialize());
148148
additionalProperties.put("urlParamReplacer", new LambdaHelper.UrlParamReplacerLambda());
149+
additionalProperties.put("urlSerializedParamReplacer", new LambdaHelper.UrlSerializedParamReplacerLambda());
149150
additionalProperties.put("tokenizedUrlParamReplacer", new LambdaHelper.TokenizedUrlParamReplacerLambda());
151+
additionalProperties.put("tokenizedUrlSerializedParamReplacer", new LambdaHelper.TokenizedUrlSerializedParamReplacerLambda());
150152
additionalProperties.put("apiFolderName", new LambdaHelper.ApiFolderName());
151153
additionalProperties.put("removeBrackets", new LambdaHelper.RemoveText("[]"));
152154
additionalProperties.put("removeFowardslash", new LambdaHelper.RemoveText("/"));

packages/@ama-sdk/schematics/schematics/typescript/core/openapi-codegen-typescript/src/main/java/com/amadeus/codegen/ts/LambdaHelper.java

+35-2
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,29 @@ public String formatFragment(String fragment) {
5959
* Replaces placeholders in the URL such as
6060
* /carts/{cartId}/travelers
6161
* to
62-
* /carts/${serializedPathParams['cartId']}/travelers
62+
* /carts/${data['cartId']}/travelers
6363
*/
6464
public static class UrlParamReplacerLambda extends CustomLambda {
6565

6666
public UrlParamReplacerLambda() {}
6767

68+
@Override
69+
public String formatFragment(String fragment) {
70+
return fragment.replaceAll("\\{([\\w_-]+)\\}", "\\${data['$1']}");
71+
}
72+
73+
}
74+
75+
/**
76+
* Replaces placeholders in the URL such as
77+
* /carts/{cartId}/travelers
78+
* to
79+
* /carts/${serializedPathParams['cartId']}/travelers
80+
*/
81+
public static class UrlSerializedParamReplacerLambda extends CustomLambda {
82+
83+
public UrlSerializedParamReplacerLambda() {}
84+
6885
@Override
6986
public String formatFragment(String fragment) {
7087
return fragment.replaceAll("\\{([\\w_-]+)\\}", "\\${serializedPathParams['$1']}");
@@ -76,12 +93,28 @@ public String formatFragment(String fragment) {
7693
* Replaces placeholders in the URL such as
7794
* /carts/{cartId}/travelers
7895
* to
79-
* /carts/${this.piiParamTokens['$1'] || serializedPathParams['$1']}/travelers
96+
* /carts/${this.piiParamTokens['$1'] || data['$1']}/travelers
8097
*/
8198
public static class TokenizedUrlParamReplacerLambda extends CustomLambda {
8299

83100
public TokenizedUrlParamReplacerLambda() {}
84101

102+
@Override
103+
public String formatFragment(String fragment) {
104+
return fragment.replaceAll("\\{([\\w_-]+)\\}", "\\${this.piiParamTokens['$1'] || data['$1']}");
105+
}
106+
}
107+
108+
/**
109+
* Replaces placeholders in the URL such as
110+
* /carts/{cartId}/travelers
111+
* to
112+
* /carts/${this.piiParamTokens['$1'] || serializedPathParams['$1']}/travelers
113+
*/
114+
public static class TokenizedUrlSerializedParamReplacerLambda extends CustomLambda {
115+
116+
public TokenizedUrlSerializedParamReplacerLambda() {}
117+
85118
@Override
86119
public String formatFragment(String fragment) {
87120
return fragment.replaceAll("\\{([\\w_-]+)\\}", "\\${this.piiParamTokens['$1'] || serializedPathParams['$1']}");

0 commit comments

Comments
 (0)