Skip to content

Commit bca9722

Browse files
authored
Merge pull request #3 from jeremy-carbonne/bad-response-type
fixed an error when the schema type is `string`
2 parents a250298 + d34557f commit bca9722

File tree

4 files changed

+147
-29
lines changed

4 files changed

+147
-29
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ bin/
55
.settings/
66
.classpath
77
.project
8+
.factorypath
89
swagger-codegen-cli-2.3.1.jar

example/api.ts

Lines changed: 117 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -192,19 +192,26 @@ export class BaseAPI {
192192
const { url, init } = this.createFetchParams(context);
193193
const response = await this.fetchApi(url, init);
194194
if (response.status >= 200 && response.status < 300) {
195-
if (context.responseType === 'JSON') {
196-
const result = await response.json() as T;
197-
return transformPropertyNames(result, context.modelPropertyNaming);
195+
switch(context.responseType) {
196+
case 'JSON':
197+
const result = await response.json() as T;
198+
return transformPropertyNames(result, context.modelPropertyNaming);
199+
case 'text':
200+
return await response.text() as any as T;
201+
default:
202+
return response as any as T;
198203
}
199-
return response as any as T;
200204
}
201205
throw response;
202206
}
203207

204208
private createFetchParams(context: RequestOpts) {
205209
let url = this.configuration.basePath + context.path;
206-
if (context.query !== undefined) {
207-
url += '?' + querystring(context.query);
210+
if (context.query !== undefined && Object.keys(context.query).length !== 0) {
211+
// only add the querystring to the URL if there are query parameters.
212+
// this is done to avoid urls ending with a "?" character which buggy webservers
213+
// do not handle correctly sometimes.
214+
url += '?' + querystring(context.query);
208215
}
209216
const body = context.body instanceof FormData ? context.body : JSON.stringify(context.body);
210217
const init = {
@@ -230,10 +237,26 @@ export class BaseAPI {
230237
}
231238
return response;
232239
}
240+
241+
/**
242+
* https://swagger.io/docs/specification/2-0/describing-responses/
243+
*
244+
* If the response type for a given API is a 'string' we need to avoid
245+
* parsing the response as json because JSON.parse("some string") will
246+
* fail when the string isn't actually JSON.
247+
*/
248+
protected getResponseType(returnType: string): ResponseType {
249+
switch (returnType) {
250+
case 'string':
251+
return 'text'
252+
default:
253+
return 'JSON'
254+
}
255+
}
233256
};
234257

235258
export class RequiredError extends Error {
236-
name: "RequiredError"
259+
name = 'RequiredError';
237260
constructor(public field: string, msg?: string) {
238261
super(msg);
239262
}
@@ -258,6 +281,15 @@ export class PetApi extends BaseAPI {
258281

259282
headerParameters['Content-Type'] = 'application/json';
260283

284+
if (this.configuration && this.configuration.accessToken) {
285+
// oauth required
286+
if (typeof this.configuration.accessToken === 'function') {
287+
headerParameters["Authorization"] = this.configuration.accessToken("petstore_auth", ["write:pets", "read:pets"]);
288+
} else {
289+
headerParameters["Authorization"] = this.configuration.accessToken;
290+
}
291+
}
292+
261293
return this.request<Response>({
262294
path: `/pet`,
263295
method: 'POST',
@@ -278,6 +310,19 @@ export class PetApi extends BaseAPI {
278310

279311
const headerParameters: HTTPHeaders = {};
280312

313+
if (requestParameters.apiKey !== undefined && requestParameters.apiKey !== null) {
314+
headerParameters['api_key'] = String(requestParameters.apiKey);
315+
}
316+
317+
if (this.configuration && this.configuration.accessToken) {
318+
// oauth required
319+
if (typeof this.configuration.accessToken === 'function') {
320+
headerParameters["Authorization"] = this.configuration.accessToken("petstore_auth", ["write:pets", "read:pets"]);
321+
} else {
322+
headerParameters["Authorization"] = this.configuration.accessToken;
323+
}
324+
}
325+
281326
return this.request<Response>({
282327
path: `/pet/{petId}`.replace(`{${"petId"}}`, encodeURIComponent(String(requestParameters.petId))),
283328
method: 'DELETE',
@@ -303,12 +348,21 @@ export class PetApi extends BaseAPI {
303348

304349
const headerParameters: HTTPHeaders = {};
305350

351+
if (this.configuration && this.configuration.accessToken) {
352+
// oauth required
353+
if (typeof this.configuration.accessToken === 'function') {
354+
headerParameters["Authorization"] = this.configuration.accessToken("petstore_auth", ["write:pets", "read:pets"]);
355+
} else {
356+
headerParameters["Authorization"] = this.configuration.accessToken;
357+
}
358+
}
359+
306360
return this.request<Array<Pet>>({
307361
path: `/pet/findByStatus`,
308362
method: 'GET',
309363
headers: headerParameters,
310364
query: queryParameters,
311-
responseType: 'JSON',
365+
responseType: this.getResponseType('Array<Pet>'),
312366
modelPropertyNaming: 'camelCase',
313367
});
314368
}
@@ -330,12 +384,21 @@ export class PetApi extends BaseAPI {
330384

331385
const headerParameters: HTTPHeaders = {};
332386

387+
if (this.configuration && this.configuration.accessToken) {
388+
// oauth required
389+
if (typeof this.configuration.accessToken === 'function') {
390+
headerParameters["Authorization"] = this.configuration.accessToken("petstore_auth", ["write:pets", "read:pets"]);
391+
} else {
392+
headerParameters["Authorization"] = this.configuration.accessToken;
393+
}
394+
}
395+
333396
return this.request<Array<Pet>>({
334397
path: `/pet/findByTags`,
335398
method: 'GET',
336399
headers: headerParameters,
337400
query: queryParameters,
338-
responseType: 'JSON',
401+
responseType: this.getResponseType('Array<Pet>'),
339402
modelPropertyNaming: 'camelCase',
340403
});
341404
}
@@ -351,11 +414,15 @@ export class PetApi extends BaseAPI {
351414

352415
const headerParameters: HTTPHeaders = {};
353416

417+
if (this.configuration && this.configuration.apiKey) {
418+
headerParameters["api_key"] = this.configuration.apiKey("api_key"); // api_key authentication
419+
}
420+
354421
return this.request<Pet>({
355422
path: `/pet/{petId}`.replace(`{${"petId"}}`, encodeURIComponent(String(requestParameters.petId))),
356423
method: 'GET',
357424
headers: headerParameters,
358-
responseType: 'JSON',
425+
responseType: this.getResponseType('Pet'),
359426
modelPropertyNaming: 'camelCase',
360427
});
361428
}
@@ -373,6 +440,15 @@ export class PetApi extends BaseAPI {
373440

374441
headerParameters['Content-Type'] = 'application/json';
375442

443+
if (this.configuration && this.configuration.accessToken) {
444+
// oauth required
445+
if (typeof this.configuration.accessToken === 'function') {
446+
headerParameters["Authorization"] = this.configuration.accessToken("petstore_auth", ["write:pets", "read:pets"]);
447+
} else {
448+
headerParameters["Authorization"] = this.configuration.accessToken;
449+
}
450+
}
451+
376452
return this.request<Response>({
377453
path: `/pet`,
378454
method: 'PUT',
@@ -458,7 +534,7 @@ export class PetApi extends BaseAPI {
458534
method: 'POST',
459535
headers: headerParameters,
460536
body: formData,
461-
responseType: 'JSON',
537+
responseType: this.getResponseType('ApiResponse'),
462538
modelPropertyNaming: 'camelCase',
463539
});
464540
}
@@ -496,11 +572,15 @@ export class StoreApi extends BaseAPI {
496572
async getInventory(): Promise<{ [key: string]: number; }> {
497573
const headerParameters: HTTPHeaders = {};
498574

575+
if (this.configuration && this.configuration.apiKey) {
576+
headerParameters["api_key"] = this.configuration.apiKey("api_key"); // api_key authentication
577+
}
578+
499579
return this.request<{ [key: string]: number; }>({
500580
path: `/store/inventory`,
501581
method: 'GET',
502582
headers: headerParameters,
503-
responseType: 'JSON',
583+
responseType: this.getResponseType('{ [key: string]: number; }'),
504584
modelPropertyNaming: 'camelCase',
505585
});
506586
}
@@ -520,7 +600,7 @@ export class StoreApi extends BaseAPI {
520600
path: `/store/order/{orderId}`.replace(`{${"orderId"}}`, encodeURIComponent(String(requestParameters.orderId))),
521601
method: 'GET',
522602
headers: headerParameters,
523-
responseType: 'JSON',
603+
responseType: this.getResponseType('Order'),
524604
modelPropertyNaming: 'camelCase',
525605
});
526606
}
@@ -543,7 +623,7 @@ export class StoreApi extends BaseAPI {
543623
method: 'POST',
544624
headers: headerParameters,
545625
body: requestParameters.body,
546-
responseType: 'JSON',
626+
responseType: this.getResponseType('Order'),
547627
modelPropertyNaming: 'camelCase',
548628
});
549629
}
@@ -655,7 +735,7 @@ export class UserApi extends BaseAPI {
655735
path: `/user/{username}`.replace(`{${"username"}}`, encodeURIComponent(String(requestParameters.username))),
656736
method: 'GET',
657737
headers: headerParameters,
658-
responseType: 'JSON',
738+
responseType: this.getResponseType('User'),
659739
modelPropertyNaming: 'camelCase',
660740
});
661741
}
@@ -690,7 +770,7 @@ export class UserApi extends BaseAPI {
690770
method: 'GET',
691771
headers: headerParameters,
692772
query: queryParameters,
693-
responseType: 'JSON',
773+
responseType: this.getResponseType('string'),
694774
modelPropertyNaming: 'camelCase',
695775
});
696776
}
@@ -775,11 +855,12 @@ export class Configuration {
775855
this.middleware = conf.middleware || [];
776856
this.username = conf.username;
777857
this.password = conf.password;
778-
if (conf.apiKey) {
779-
this.apiKey = typeof conf.apiKey === 'function' ? conf.apiKey : () => conf.apiKey;
858+
const { apiKey, accessToken } = conf;
859+
if (apiKey) {
860+
this.apiKey = typeof apiKey === 'function' ? apiKey : () => apiKey;
780861
}
781-
if (conf.accessToken) {
782-
this.accessToken = typeof conf.accessToken === 'function' ? conf.accessToken : () => conf.accessToken;
862+
if (accessToken) {
863+
this.accessToken = typeof accessToken === 'function' ? accessToken : () => accessToken;
783864
}
784865
}
785866
}
@@ -796,13 +877,15 @@ export interface FetchParams {
796877
init: RequestInit;
797878
}
798879

880+
type ResponseType = 'JSON' | 'text';
881+
799882
interface RequestOpts {
800883
path: string;
801884
method: HTTPMethod;
802885
headers: HTTPHeaders;
803886
query?: HTTPQuery;
804887
body?: HTTPBody;
805-
responseType?: 'JSON';
888+
responseType?: ResponseType;
806889
modelPropertyNaming: ModelPropertyNaming;
807890
}
808891

@@ -824,8 +907,20 @@ export interface Middleware {
824907
post?(fetch: FetchAPI, url: string, init: RequestInit, response: Response): Promise<Response | undefined | void>;
825908
}
826909

910+
function capitalize(word: string) {
911+
return word.charAt(0).toUpperCase() + word.slice(1);
912+
}
913+
914+
function toPascalCase(name: string) {
915+
return name
916+
.split('_')
917+
.map(capitalize)
918+
.join('');
919+
}
920+
827921
function toCamelCase(name: string) {
828-
return (name.charAt(0).toLowerCase() + name.slice(1) || name).toString();
922+
const pascalCase = toPascalCase(name);
923+
return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
829924
}
830925

831926
function applyPropertyNameConverter(json: any, converter: (name: string) => string) {

petstore-example.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ set -e
33

44
mvn clean package
55

6-
CUSTOM_GEN_JAR=./target/TypescriptBrowser-swagger-codegen-0.0.1-shaded.jar
6+
CUSTOM_GEN_JAR=./target/TypescriptBrowser-swagger-codegen-*-shaded.jar
77

88
java -jar ${CUSTOM_GEN_JAR} generate \
99
-l TypescriptBrowser \

src/main/resources/TypescriptBrowser/api.mustache

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,15 @@ export class BaseAPI {
5454
const { url, init } = this.createFetchParams(context);
5555
const response = await this.fetchApi(url, init);
5656
if (response.status >= 200 && response.status < 300) {
57-
if (context.responseType === 'JSON') {
58-
const result = await response.json() as T;
59-
return transformPropertyNames(result, context.modelPropertyNaming);
57+
switch(context.responseType) {
58+
case 'JSON':
59+
const result = await response.json() as T;
60+
return transformPropertyNames(result, context.modelPropertyNaming);
61+
case 'text':
62+
return await response.text() as any as T;
63+
default:
64+
return response as any as T;
6065
}
61-
return response as any as T;
6266
}
6367
throw response;
6468
}
@@ -95,6 +99,22 @@ export class BaseAPI {
9599
}
96100
return response;
97101
}
102+
103+
/**
104+
* https://swagger.io/docs/specification/2-0/describing-responses/
105+
*
106+
* If the response type for a given API is a 'string' we need to avoid
107+
* parsing the response as json because JSON.parse("some string") will
108+
* fail when the string isn't actually JSON.
109+
*/
110+
protected getResponseType(returnType: string): ResponseType {
111+
switch (returnType) {
112+
case 'string':
113+
return 'text'
114+
default:
115+
return 'JSON'
116+
}
117+
}
98118
};
99119

100120
export class RequiredError extends Error {
@@ -260,7 +280,7 @@ export class {{classname}} extends BaseAPI {
260280
body: formData,
261281
{{/hasFormParams}}
262282
{{#returnType}}
263-
responseType: 'JSON',
283+
responseType: this.getResponseType('{{{returnType}}}'),
264284
{{/returnType}}
265285
modelPropertyNaming: '{{modelPropertyNaming}}',
266286
});
@@ -328,13 +348,15 @@ export interface FetchParams {
328348
init: RequestInit;
329349
}
330350

351+
type ResponseType = 'JSON' | 'text';
352+
331353
interface RequestOpts {
332354
path: string;
333355
method: HTTPMethod;
334356
headers: HTTPHeaders;
335357
query?: HTTPQuery;
336358
body?: HTTPBody;
337-
responseType?: 'JSON';
359+
responseType?: ResponseType;
338360
modelPropertyNaming: ModelPropertyNaming;
339361
}
340362

0 commit comments

Comments
 (0)