Skip to content

Commit e91fdf5

Browse files
macjohnnyfvarose
authored andcommitted
[angular-typescript] fix using form data (swagger-api#6574)
* swagger-api#6457: fix sending form params * swagger-api#6457: fix sending form params using files * swagger-api#6457: fix sending form params with files for angular 4.3 * swagger-api#6457: generate samples * swagger-api#6457: [typescript-angular] fix form data submission in IE/Edge * re-apply [typescript-angular] add customized encoder to use '+' char in query parameter swagger-api#6306 (swagger-api#6334) * adapt for HttpClient: [typescript-angular] add customized encoder to use '+' char in query parameter swagger-api#6306 (swagger-api#6334) * generate samples * swagger-api#6457: fix url parameter encoder imports for angular <4.3 * swagger-api#6457: generate samples
1 parent b29c514 commit e91fdf5

File tree

24 files changed

+345
-195
lines changed

24 files changed

+345
-195
lines changed

bin/typescript-angular-v4.3-petstore-with-npm.sh

100644100755
File mode changed.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
set executable=.\modules\swagger-codegen-cli\target\swagger-codegen-cli.jar
2+
3+
If Not Exist %executable% (
4+
mvn clean package
5+
)
6+
7+
REM set JAVA_OPTS=%JAVA_OPTS% -Xmx1024M
8+
set ags=generate -i modules\swagger-codegen\src\test\resources\2_0\petstore.yaml -c bin\typescript-petstore-npm.json -l typescript-angular -o samples\client\petstore\typescript-angular-v4.3\npm --additional-properties ngVersion=4.3
9+
10+
java %JAVA_OPTS% -jar %executable% %ags%

modules/swagger-codegen/src/main/resources/typescript-angular/api.service.mustache

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ import { {{classname}} } from '../{{filename}}';
2020

2121
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
2222
import { Configuration } from '../configuration';
23+
{{#useHttpClient}}
24+
import { CustomHttpUrlEncodingCodec } from '../encoder';
25+
{{/useHttpClient}}
26+
{{^useHttpClient}}
2327
import { CustomQueryEncoderHelper } from '../encoder';
28+
{{/useHttpClient}}
2429
{{#withInterfaces}}
2530
import { {{classname}}Interface } from './{{classname}}Interface';
2631
{{/withInterfaces}}
@@ -69,11 +74,6 @@ export class {{classname}} {
6974
}
7075

7176

72-
public isJsonMime(mime: string): boolean {
73-
const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
74-
return mime != null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
75-
}
76-
7777
{{^useHttpClient}}
7878
{{! not sure why we used to generate a second method here rather than inlining those few lines of code,
7979
but let's keep it for now for the sake of backwards compatiblity. }}
@@ -121,7 +121,12 @@ export class {{classname}} {
121121
{{/allParams}}
122122

123123
{{#hasQueryParams}}
124-
let queryParameters = new {{#useHttpClient}}HttpParams{{/useHttpClient}}{{^useHttpClient}}URLSearchParams{{/useHttpClient}}();
124+
{{#useHttpClient}}
125+
let queryParameters = new HttpParams({encoder: new CustomHttpUrlEncodingCodec()});
126+
{{/useHttpClient}}
127+
{{^useHttpClient}}
128+
let queryParameters = new URLSearchParams('', new CustomQueryEncoderHelper());
129+
{{/useHttpClient}}
125130
{{#queryParams}}
126131
{{#isListContainer}}
127132
if ({{paramName}}) {
@@ -207,39 +212,59 @@ export class {{classname}} {
207212
'{{{mediaType}}}'{{#hasMore}},{{/hasMore}}
208213
{{/consumes}}
209214
];
210-
let canConsumeForm = this.canConsumeForm(consumes);
215+
const canConsumeForm = this.canConsumeForm(consumes);
216+
217+
let formParams: { append(param: string, value: any): void; };
211218
let useForm = false;
219+
let convertFormParamsToString = false;
212220
{{#formParams}}
213221
{{#isFile}}
222+
// use FormData to transmit files using content-type "multipart/form-data"
223+
// see https://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data
214224
useForm = canConsumeForm;
215225
{{/isFile}}
216226
{{/formParams}}
217-
let formParams = new (useForm ? FormData : URLSearchParams as any)() as {
218-
set(param: string, value: any): void;
219-
};
227+
if (useForm) {
228+
formParams = new FormData();
229+
} else {
230+
{{#useHttpClient}}
231+
formParams = new HttpParams({encoder: new CustomHttpUrlEncodingCodec()});
232+
{{/useHttpClient}}
233+
{{^useHttpClient}}
234+
// TODO: this fails if a parameter is a file, the api can't consume "multipart/form-data" and a blob is passed.
235+
convertFormParamsToString = true;
236+
formParams = new URLSearchParams('', new CustomQueryEncoderHelper());
237+
// set the content-type explicitly to avoid having it set to 'text/plain'
238+
headers.set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
239+
{{/useHttpClient}}
240+
}
241+
242+
243+
220244
{{#formParams}}
221245
{{#isListContainer}}
222246
if ({{paramName}}) {
223247
{{#isCollectionFormatMulti}}
224248
{{paramName}}.forEach((element) => {
225-
formParams.append('{{baseName}}', <any>element);
249+
{{#useHttpClient}}formParams = {{/useHttpClient}}formParams.append('{{baseName}}', <any>element){{#useHttpClient}} || formParams{{/useHttpClient}};
226250
})
227251
{{/isCollectionFormatMulti}}
228252
{{^isCollectionFormatMulti}}
229-
formParams.set('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS['{{collectionFormat}}']));
253+
{{#useHttpClient}}formParams = {{/useHttpClient}}formParams.append('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS['{{collectionFormat}}'])){{#useHttpClient}} || formParams{{/useHttpClient}};
230254
{{/isCollectionFormatMulti}}
231255
}
232256
{{/isListContainer}}
233257
{{^isListContainer}}
234258
if ({{paramName}} !== undefined) {
235-
formParams.set('{{baseName}}', <any>{{paramName}});
259+
{{#useHttpClient}}formParams = {{/useHttpClient}}formParams.append('{{baseName}}', <any>{{paramName}}){{#useHttpClient}} || formParams{{/useHttpClient}};
236260
}
237261
{{/isListContainer}}
238262
{{/formParams}}
239263

240264
{{/hasFormParams}}
241265
{{#useHttpClient}}
242-
return this.httpClient.{{httpMethod}}{{^isResponseFile}}<any>{{/isResponseFile}}(`${this.basePath}{{{path}}}`, {{#bodyParam}}{{paramName}}, {{/bodyParam}}{{#hasFormParams}}formParams, {{/hasFormParams}}{
266+
return this.httpClient.{{httpMethod}}{{^isResponseFile}}<any>{{/isResponseFile}}(`${this.basePath}{{{path}}}`, {{#bodyParam}}{{paramName}}, {{/bodyParam}}
267+
{{#hasFormParams}}convertFormParamsToString ? formParams.toString() : formParams, {{/hasFormParams}}{
243268
{{#hasQueryParams}}
244269
params: queryParameters,
245270
{{/hasQueryParams}}
@@ -258,7 +283,7 @@ export class {{classname}} {
258283
body: {{paramName}} == null ? '' : JSON.stringify({{paramName}}), // https://github.com/angular/angular/issues/10612
259284
{{/bodyParam}}
260285
{{#hasFormParams}}
261-
body: formParams.toString(),
286+
body: convertFormParamsToString ? formParams.toString() : formParams,
262287
{{/hasFormParams}}
263288
{{#isResponseFile}}
264289
responseType: ResponseContentType.Blob,

modules/swagger-codegen/src/main/resources/typescript-angular/encoder.mustache

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
1-
import { QueryEncoder } from "@angular/http";
1+
{{#useHttpClient}}
2+
import { HttpUrlEncodingCodec } from '@angular/common/http';
3+
{{/useHttpClient}}
4+
{{^useHttpClient}}
5+
import { QueryEncoder } from '@angular/http';
6+
{{/useHttpClient}}
27

8+
{{#useHttpClient}}
9+
/**
10+
* CustomHttpUrlEncodingCodec
11+
* Fix plus sign (+) not encoding, so sent as blank space
12+
* See: https://github.com/angular/angular/issues/11058#issuecomment-247367318
13+
*/
14+
export class CustomHttpUrlEncodingCodec extends HttpUrlEncodingCodec {
15+
encodeKey(k: string): string {
16+
k = super.encodeKey(k);
17+
return k.replace(/\+/gi, '%2B');
18+
}
19+
encodeValue(v: string): string {
20+
v = super.encodeValue(v);
21+
return v.replace(/\+/gi, '%2B');
22+
}
23+
}
24+
{{/useHttpClient}}
25+
{{^useHttpClient}}
326
/**
427
* CustomQueryEncoderHelper
528
* Fix plus sign (+) not encoding, so sent as blank space
@@ -14,4 +37,6 @@ export class CustomQueryEncoderHelper extends QueryEncoder {
1437
v = super.encodeValue(v);
1538
return v.replace(/\+/gi, '%2B');
1639
}
17-
}
40+
}
41+
{{/useHttpClient}}
42+

samples/client/petstore/typescript-angular-v2/default/api/pet.service.ts

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,6 @@ export class PetService {
6060
}
6161

6262

63-
public isJsonMime(mime: string): boolean {
64-
const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
65-
return mime != null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
66-
}
67-
6863
/**
6964
*
7065
* @summary Add a new pet to the store
@@ -282,7 +277,7 @@ export class PetService {
282277
throw new Error('Required parameter status was null or undefined when calling findPetsByStatus.');
283278
}
284279

285-
let queryParameters = new URLSearchParams();
280+
let queryParameters = new URLSearchParams('', new CustomQueryEncoderHelper());
286281
if (status) {
287282
queryParameters.set('status', status.join(COLLECTION_FORMATS['csv']));
288283
}
@@ -321,7 +316,7 @@ export class PetService {
321316
throw new Error('Required parameter tags was null or undefined when calling findPetsByTags.');
322317
}
323318

324-
let queryParameters = new URLSearchParams();
319+
let queryParameters = new URLSearchParams('', new CustomQueryEncoderHelper());
325320
if (tags) {
326321
queryParameters.set('tags', tags.join(COLLECTION_FORMATS['csv']));
327322
}
@@ -442,22 +437,34 @@ export class PetService {
442437
let consumes: string[] = [
443438
'application/x-www-form-urlencoded'
444439
];
445-
let canConsumeForm = this.canConsumeForm(consumes);
440+
const canConsumeForm = this.canConsumeForm(consumes);
441+
442+
let formParams: { append(param: string, value: any): void; };
446443
let useForm = false;
447-
let formParams = new (useForm ? FormData : URLSearchParams as any)() as {
448-
set(param: string, value: any): void;
449-
};
444+
let convertFormParamsToString = false;
445+
if (useForm) {
446+
formParams = new FormData();
447+
} else {
448+
// TODO: this fails if a parameter is a file, the api can't consume "multipart/form-data" and a blob is passed.
449+
convertFormParamsToString = true;
450+
formParams = new URLSearchParams('', new CustomQueryEncoderHelper());
451+
// set the content-type explicitly to avoid having it set to 'text/plain'
452+
headers.set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
453+
}
454+
455+
456+
450457
if (name !== undefined) {
451-
formParams.set('name', <any>name);
458+
formParams.append('name', <any>name);
452459
}
453460
if (status !== undefined) {
454-
formParams.set('status', <any>status);
461+
formParams.append('status', <any>status);
455462
}
456463

457464
let requestOptions: RequestOptionsArgs = new RequestOptions({
458465
method: RequestMethod.Post,
459466
headers: headers,
460-
body: formParams.toString(),
467+
body: convertFormParamsToString ? formParams.toString() : formParams,
461468
withCredentials:this.configuration.withCredentials
462469
});
463470
// https://github.com/swagger-api/swagger-codegen/issues/4037
@@ -494,23 +501,37 @@ export class PetService {
494501
let consumes: string[] = [
495502
'multipart/form-data'
496503
];
497-
let canConsumeForm = this.canConsumeForm(consumes);
504+
const canConsumeForm = this.canConsumeForm(consumes);
505+
506+
let formParams: { append(param: string, value: any): void; };
498507
let useForm = false;
508+
let convertFormParamsToString = false;
509+
// use FormData to transmit files using content-type "multipart/form-data"
510+
// see https://stackoverflow.com/questions/4007969/application-x-www-form-urlencoded-or-multipart-form-data
499511
useForm = canConsumeForm;
500-
let formParams = new (useForm ? FormData : URLSearchParams as any)() as {
501-
set(param: string, value: any): void;
502-
};
512+
if (useForm) {
513+
formParams = new FormData();
514+
} else {
515+
// TODO: this fails if a parameter is a file, the api can't consume "multipart/form-data" and a blob is passed.
516+
convertFormParamsToString = true;
517+
formParams = new URLSearchParams('', new CustomQueryEncoderHelper());
518+
// set the content-type explicitly to avoid having it set to 'text/plain'
519+
headers.set('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8');
520+
}
521+
522+
523+
503524
if (additionalMetadata !== undefined) {
504-
formParams.set('additionalMetadata', <any>additionalMetadata);
525+
formParams.append('additionalMetadata', <any>additionalMetadata);
505526
}
506527
if (file !== undefined) {
507-
formParams.set('file', <any>file);
528+
formParams.append('file', <any>file);
508529
}
509530

510531
let requestOptions: RequestOptionsArgs = new RequestOptions({
511532
method: RequestMethod.Post,
512533
headers: headers,
513-
body: formParams.toString(),
534+
body: convertFormParamsToString ? formParams.toString() : formParams,
514535
withCredentials:this.configuration.withCredentials
515536
});
516537
// https://github.com/swagger-api/swagger-codegen/issues/4037

samples/client/petstore/typescript-angular-v2/default/api/store.service.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,6 @@ export class StoreService {
5959
}
6060

6161

62-
public isJsonMime(mime: string): boolean {
63-
const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
64-
return mime != null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
65-
}
66-
6762
/**
6863
* For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
6964
* @summary Delete purchase order by ID

samples/client/petstore/typescript-angular-v2/default/api/user.service.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,6 @@ export class UserService {
5959
}
6060

6161

62-
public isJsonMime(mime: string): boolean {
63-
const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
64-
return mime != null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
65-
}
66-
6762
/**
6863
* This can only be done by the logged in user.
6964
* @summary Create user
@@ -342,7 +337,7 @@ export class UserService {
342337
throw new Error('Required parameter password was null or undefined when calling loginUser.');
343338
}
344339

345-
let queryParameters = new URLSearchParams();
340+
let queryParameters = new URLSearchParams('', new CustomQueryEncoderHelper());
346341
if (username !== undefined) {
347342
queryParameters.set('username', <any>username);
348343
}

samples/client/petstore/typescript-angular-v2/default/encoder.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { QueryEncoder } from "@angular/http";
1+
import { QueryEncoder } from '@angular/http';
22

33
/**
44
* CustomQueryEncoderHelper
@@ -14,4 +14,5 @@ export class CustomQueryEncoderHelper extends QueryEncoder {
1414
v = super.encodeValue(v);
1515
return v.replace(/\+/gi, '%2B');
1616
}
17-
}
17+
}
18+

0 commit comments

Comments
 (0)