Skip to content

Commit 3c869d5

Browse files
authored
[Fix/Dart2] Resolve an exception with status 204 and no body. (#7647)
* Resolve an exception situation when a remote server returns 204 with no body. * Only return a value when needed. * Use HttpStatus codes instead of magic numbers. * Drop checking for a body as it will consume too much memory. * Cosmetic changes.
1 parent 177e536 commit 3c869d5

File tree

8 files changed

+125
-124
lines changed

8 files changed

+125
-124
lines changed

modules/openapi-generator/src/main/resources/dart2/api.mustache

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,19 @@ class {{{classname}}} {
5050
{{/hasMore}}
5151
{{/allParams}}
5252
{{#returnType}}Future<Response> {{/returnType}}{{^returnType}}Future {{/returnType}}{{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}} {{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async {
53+
{{#hasParams}}
5354
// Verify required params are set.
5455
{{#allParams}}
5556
{{#required}}
5657
if ({{{paramName}}} == null) {
57-
throw ApiException(400, 'Missing required param: {{{paramName}}}');
58+
throw ApiException(HttpStatus.badRequest, 'Missing required param: {{{paramName}}}');
5859
}
5960
{{/required}}
6061
{{/allParams}}
6162

62-
final path = '{{{path}}}'.replaceAll('{format}', 'json'){{#pathParams}}.replaceAll('{' + '{{{baseName}}}' + '}', {{{paramName}}}.toString()){{/pathParams}};
63+
{{/hasParams}}
64+
final path = '{{{path}}}'.replaceAll('{format}', 'json'){{#pathParams}}
65+
.replaceAll('{' + '{{{baseName}}}' + '}', {{{paramName}}}.toString()){{/pathParams}};
6366

6467
Object postBody{{#bodyParam}} = {{{paramName}}}{{/bodyParam}};
6568

@@ -162,31 +165,30 @@ class {{{classname}}} {
162165
{{/allParams}}
163166
{{#returnType}}Future<{{{returnType}}}> {{/returnType}}{{^returnType}}Future {{/returnType}}{{{nickname}}}({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}} {{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async {
164167
final response = await {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/required}}{{/allParams}}{{#hasOptionalParams}} {{#allParams}}{{^required}}{{{paramName}}}: {{{paramName}}}{{#hasMore}}, {{/hasMore}}{{/required}}{{/allParams}} {{/hasOptionalParams}});
165-
if (response.statusCode >= 400) {
168+
if (response.statusCode >= HttpStatus.badRequest) {
166169
throw ApiException(response.statusCode, _decodeBodyBytes(response));
167170
}
168-
if (response.body != null) {
171+
{{#returnType}}
172+
// When a remote server returns no body with a status of 204, we shall not decode it.
173+
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
174+
// FormatException when trying to decode an empty string.
175+
if (response.body != null && response.statusCode != HttpStatus.noContent) {
169176
{{#isListContainer}}
170-
{{#returnType}}
171177
return (apiClient.deserialize(_decodeBodyBytes(response), '{{{returnType}}}') as List)
172178
.map((item) => item as {{{returnBaseType}}})
173179
.toList(growable: false);
174-
{{/returnType}}
175180
{{/isListContainer}}
176181
{{^isListContainer}}
177182
{{#isMap}}
178-
{{#returnType}}
179183
return {{{returnType}}}.from(apiClient.deserialize(_decodeBodyBytes(response), '{{{returnType}}}'));
180-
{{/returnType}}
181184
{{/isMap}}
182185
{{^isMap}}
183-
{{#returnType}}
184186
return apiClient.deserialize(_decodeBodyBytes(response), '{{{returnType}}}') as {{{returnType}}};
185-
{{/returnType}}
186187
{{/isMap}}
187188
{{/isListContainer}}
188189
}
189-
return{{#returnType}} null{{/returnType}};
190+
return null;
191+
{{/returnType}}
190192
}
191193
{{/operation}}
192194
}

modules/openapi-generator/src/main/resources/dart2/api_client.mustache

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,16 @@ class ApiClient {
125125
case 'GET': return await _client.get(url, headers: nullableHeaderParams);
126126
}
127127
} on SocketException catch (e, trace) {
128-
throw ApiException.withInner(400, 'Socket operation failed: $method $path', e, trace);
128+
throw ApiException.withInner(HttpStatus.badRequest, 'Socket operation failed: $method $path', e, trace);
129129
} on TlsException catch (e, trace) {
130-
throw ApiException.withInner(400, 'TLS/SSL communication failed: $method $path', e, trace);
130+
throw ApiException.withInner(HttpStatus.badRequest, 'TLS/SSL communication failed: $method $path', e, trace);
131131
} on IOException catch (e, trace) {
132-
throw ApiException.withInner(400, 'I/O operation failed: $method $path', e, trace);
132+
throw ApiException.withInner(HttpStatus.badRequest, 'I/O operation failed: $method $path', e, trace);
133133
} on Exception catch (e, trace) {
134-
throw ApiException.withInner(400, 'Exception occurred: $method $path', e, trace);
134+
throw ApiException.withInner(HttpStatus.badRequest, 'Exception occurred: $method $path', e, trace);
135135
}
136136

137-
throw ApiException(400, 'Invalid HTTP operation: $method $path');
137+
throw ApiException(HttpStatus.badRequest, 'Invalid HTTP operation: $method $path');
138138
}
139139

140140
dynamic _deserialize(dynamic value, String targetType, {bool growable}) {
@@ -182,9 +182,9 @@ class ApiClient {
182182
break;
183183
}
184184
} on Exception catch (e, stack) {
185-
throw ApiException.withInner(500, 'Exception during deserialization.', e, stack);
185+
throw ApiException.withInner(HttpStatus.internalServerError, 'Exception during deserialization.', e, stack);
186186
}
187-
throw ApiException(500, 'Could not find a suitable class for deserialization');
187+
throw ApiException(HttpStatus.internalServerError, 'Could not find a suitable class for deserialization');
188188
}
189189

190190
/// Update query and header parameters based on authentication settings.

modules/openapi-generator/src/main/resources/dart2/api_helper.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ String parameterToString(dynamic value) {
5555
/// content type. Otherwise, returns the decoded body as decoded by dart:http package.
5656
String _decodeBodyBytes(Response response) {
5757
final contentType = response.headers['content-type'];
58-
return contentType != null && contentType.contains('application/json')
59-
? utf8.decode(response.bodyBytes)
58+
return contentType != null && contentType.toLowerCase().startsWith('application/json')
59+
? response.bodyBytes == null ? null : utf8.decode(response.bodyBytes)
6060
: response.body;
6161
}

samples/client/petstore/dart2/petstore_client_lib/lib/api/pet_api.dart

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class PetApi {
2626
Future addPetWithHttpInfo(Pet body) async {
2727
// Verify required params are set.
2828
if (body == null) {
29-
throw ApiException(400, 'Missing required param: body');
29+
throw ApiException(HttpStatus.badRequest, 'Missing required param: body');
3030
}
3131

3232
final path = '/pet'.replaceAll('{format}', 'json');
@@ -74,12 +74,9 @@ class PetApi {
7474
/// Pet object that needs to be added to the store
7575
Future addPet(Pet body) async {
7676
final response = await addPetWithHttpInfo(body);
77-
if (response.statusCode >= 400) {
77+
if (response.statusCode >= HttpStatus.badRequest) {
7878
throw ApiException(response.statusCode, _decodeBodyBytes(response));
7979
}
80-
if (response.body != null) {
81-
}
82-
return;
8380
}
8481

8582
/// Deletes a pet
@@ -95,10 +92,11 @@ class PetApi {
9592
Future deletePetWithHttpInfo(int petId, { String apiKey }) async {
9693
// Verify required params are set.
9794
if (petId == null) {
98-
throw ApiException(400, 'Missing required param: petId');
95+
throw ApiException(HttpStatus.badRequest, 'Missing required param: petId');
9996
}
10097

101-
final path = '/pet/{petId}'.replaceAll('{format}', 'json').replaceAll('{' + 'petId' + '}', petId.toString());
98+
final path = '/pet/{petId}'.replaceAll('{format}', 'json')
99+
.replaceAll('{' + 'petId' + '}', petId.toString());
102100

103101
Object postBody;
104102

@@ -146,12 +144,9 @@ class PetApi {
146144
/// * [String] apiKey:
147145
Future deletePet(int petId, { String apiKey }) async {
148146
final response = await deletePetWithHttpInfo(petId, apiKey: apiKey );
149-
if (response.statusCode >= 400) {
147+
if (response.statusCode >= HttpStatus.badRequest) {
150148
throw ApiException(response.statusCode, _decodeBodyBytes(response));
151149
}
152-
if (response.body != null) {
153-
}
154-
return;
155150
}
156151

157152
/// Finds Pets by status
@@ -167,7 +162,7 @@ class PetApi {
167162
Future<Response> findPetsByStatusWithHttpInfo(List<String> status) async {
168163
// Verify required params are set.
169164
if (status == null) {
170-
throw ApiException(400, 'Missing required param: status');
165+
throw ApiException(HttpStatus.badRequest, 'Missing required param: status');
171166
}
172167

173168
final path = '/pet/findByStatus'.replaceAll('{format}', 'json');
@@ -218,10 +213,13 @@ class PetApi {
218213
/// Status values that need to be considered for filter
219214
Future<List<Pet>> findPetsByStatus(List<String> status) async {
220215
final response = await findPetsByStatusWithHttpInfo(status);
221-
if (response.statusCode >= 400) {
216+
if (response.statusCode >= HttpStatus.badRequest) {
222217
throw ApiException(response.statusCode, _decodeBodyBytes(response));
223218
}
224-
if (response.body != null) {
219+
// When a remote server returns no body with a status of 204, we shall not decode it.
220+
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
221+
// FormatException when trying to decode an empty string.
222+
if (response.body != null && response.statusCode != HttpStatus.noContent) {
225223
return (apiClient.deserialize(_decodeBodyBytes(response), 'List<Pet>') as List)
226224
.map((item) => item as Pet)
227225
.toList(growable: false);
@@ -242,7 +240,7 @@ class PetApi {
242240
Future<Response> findPetsByTagsWithHttpInfo(List<String> tags) async {
243241
// Verify required params are set.
244242
if (tags == null) {
245-
throw ApiException(400, 'Missing required param: tags');
243+
throw ApiException(HttpStatus.badRequest, 'Missing required param: tags');
246244
}
247245

248246
final path = '/pet/findByTags'.replaceAll('{format}', 'json');
@@ -293,10 +291,13 @@ class PetApi {
293291
/// Tags to filter by
294292
Future<List<Pet>> findPetsByTags(List<String> tags) async {
295293
final response = await findPetsByTagsWithHttpInfo(tags);
296-
if (response.statusCode >= 400) {
294+
if (response.statusCode >= HttpStatus.badRequest) {
297295
throw ApiException(response.statusCode, _decodeBodyBytes(response));
298296
}
299-
if (response.body != null) {
297+
// When a remote server returns no body with a status of 204, we shall not decode it.
298+
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
299+
// FormatException when trying to decode an empty string.
300+
if (response.body != null && response.statusCode != HttpStatus.noContent) {
300301
return (apiClient.deserialize(_decodeBodyBytes(response), 'List<Pet>') as List)
301302
.map((item) => item as Pet)
302303
.toList(growable: false);
@@ -317,10 +318,11 @@ class PetApi {
317318
Future<Response> getPetByIdWithHttpInfo(int petId) async {
318319
// Verify required params are set.
319320
if (petId == null) {
320-
throw ApiException(400, 'Missing required param: petId');
321+
throw ApiException(HttpStatus.badRequest, 'Missing required param: petId');
321322
}
322323

323-
final path = '/pet/{petId}'.replaceAll('{format}', 'json').replaceAll('{' + 'petId' + '}', petId.toString());
324+
final path = '/pet/{petId}'.replaceAll('{format}', 'json')
325+
.replaceAll('{' + 'petId' + '}', petId.toString());
324326

325327
Object postBody;
326328

@@ -367,10 +369,13 @@ class PetApi {
367369
/// ID of pet to return
368370
Future<Pet> getPetById(int petId) async {
369371
final response = await getPetByIdWithHttpInfo(petId);
370-
if (response.statusCode >= 400) {
372+
if (response.statusCode >= HttpStatus.badRequest) {
371373
throw ApiException(response.statusCode, _decodeBodyBytes(response));
372374
}
373-
if (response.body != null) {
375+
// When a remote server returns no body with a status of 204, we shall not decode it.
376+
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
377+
// FormatException when trying to decode an empty string.
378+
if (response.body != null && response.statusCode != HttpStatus.noContent) {
374379
return apiClient.deserialize(_decodeBodyBytes(response), 'Pet') as Pet;
375380
}
376381
return null;
@@ -387,7 +392,7 @@ class PetApi {
387392
Future updatePetWithHttpInfo(Pet body) async {
388393
// Verify required params are set.
389394
if (body == null) {
390-
throw ApiException(400, 'Missing required param: body');
395+
throw ApiException(HttpStatus.badRequest, 'Missing required param: body');
391396
}
392397

393398
final path = '/pet'.replaceAll('{format}', 'json');
@@ -435,12 +440,9 @@ class PetApi {
435440
/// Pet object that needs to be added to the store
436441
Future updatePet(Pet body) async {
437442
final response = await updatePetWithHttpInfo(body);
438-
if (response.statusCode >= 400) {
443+
if (response.statusCode >= HttpStatus.badRequest) {
439444
throw ApiException(response.statusCode, _decodeBodyBytes(response));
440445
}
441-
if (response.body != null) {
442-
}
443-
return;
444446
}
445447

446448
/// Updates a pet in the store with form data
@@ -460,10 +462,11 @@ class PetApi {
460462
Future updatePetWithFormWithHttpInfo(int petId, { String name, String status }) async {
461463
// Verify required params are set.
462464
if (petId == null) {
463-
throw ApiException(400, 'Missing required param: petId');
465+
throw ApiException(HttpStatus.badRequest, 'Missing required param: petId');
464466
}
465467

466-
final path = '/pet/{petId}'.replaceAll('{format}', 'json').replaceAll('{' + 'petId' + '}', petId.toString());
468+
final path = '/pet/{petId}'.replaceAll('{format}', 'json')
469+
.replaceAll('{' + 'petId' + '}', petId.toString());
467470

468471
Object postBody;
469472

@@ -528,12 +531,9 @@ class PetApi {
528531
/// Updated status of the pet
529532
Future updatePetWithForm(int petId, { String name, String status }) async {
530533
final response = await updatePetWithFormWithHttpInfo(petId, name: name, status: status );
531-
if (response.statusCode >= 400) {
534+
if (response.statusCode >= HttpStatus.badRequest) {
532535
throw ApiException(response.statusCode, _decodeBodyBytes(response));
533536
}
534-
if (response.body != null) {
535-
}
536-
return;
537537
}
538538

539539
/// uploads an image
@@ -553,10 +553,11 @@ class PetApi {
553553
Future<Response> uploadFileWithHttpInfo(int petId, { String additionalMetadata, MultipartFile file }) async {
554554
// Verify required params are set.
555555
if (petId == null) {
556-
throw ApiException(400, 'Missing required param: petId');
556+
throw ApiException(HttpStatus.badRequest, 'Missing required param: petId');
557557
}
558558

559-
final path = '/pet/{petId}/uploadImage'.replaceAll('{format}', 'json').replaceAll('{' + 'petId' + '}', petId.toString());
559+
final path = '/pet/{petId}/uploadImage'.replaceAll('{format}', 'json')
560+
.replaceAll('{' + 'petId' + '}', petId.toString());
560561

561562
Object postBody;
562563

@@ -619,10 +620,13 @@ class PetApi {
619620
/// file to upload
620621
Future<ApiResponse> uploadFile(int petId, { String additionalMetadata, MultipartFile file }) async {
621622
final response = await uploadFileWithHttpInfo(petId, additionalMetadata: additionalMetadata, file: file );
622-
if (response.statusCode >= 400) {
623+
if (response.statusCode >= HttpStatus.badRequest) {
623624
throw ApiException(response.statusCode, _decodeBodyBytes(response));
624625
}
625-
if (response.body != null) {
626+
// When a remote server returns no body with a status of 204, we shall not decode it.
627+
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
628+
// FormatException when trying to decode an empty string.
629+
if (response.body != null && response.statusCode != HttpStatus.noContent) {
626630
return apiClient.deserialize(_decodeBodyBytes(response), 'ApiResponse') as ApiResponse;
627631
}
628632
return null;

0 commit comments

Comments
 (0)