Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/generators/dart.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl

<ul class="column-ul">
<li>abstract</li>
<li>apiclient</li>
<li>as</li>
<li>assert</li>
<li>async</li>
Expand Down Expand Up @@ -128,6 +129,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
<li>true</li>
<li>try</li>
<li>typedef</li>
<li>value</li>
<li>var</li>
<li>void</li>
<li>while</li>
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,6 @@ var
void
while
with
yield
yield
value
apiClient
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a valid solution. If there are clashes then you need to find another solution.

Copy link
Contributor Author

@0xNF 0xNF Feb 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while I agree it's kind of a dirty solution, I'm not sure why it's not a valid solution. We have a mechanism already built into the generator logic for eliminating collisions, why not use it? What is the value in the keeping the purity of the list to what is strictly in the Dart language's reserved word list?

Would you be opposed to adding a second list, say openapitools_reserved_words_dart.txt that contains things owned by the generator itself, that then uses the same system for collision removal?

66 changes: 33 additions & 33 deletions modules/openapi-generator/src/main/resources/dart2/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,22 @@ class {{{classname}}} {
{{/allParams}}
Future<Response> {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async {
// ignore: prefer_const_declarations
final path = r'{{{path}}}'{{#pathParams}}
final _path = r'{{{path}}}'{{#pathParams}}
.replaceAll({{=<% %>=}}'{<% baseName %>}'<%={{ }}=%>, {{{paramName}}}{{^isString}}.toString(){{/isString}}){{/pathParams}};

// ignore: prefer_final_locals
Object? postBody{{#bodyParam}} = {{{paramName}}}{{/bodyParam}};
Object? _postBody{{#bodyParam}} = {{{paramName}}}{{/bodyParam}};

final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
final __queryParams = <QueryParam>[];
final _headerParams = <String, String>{};
final _formParams = <String, String>{};
{{#hasQueryParams}}

{{#queryParams}}
{{^required}}
if ({{{paramName}}} != null) {
{{/required}}
queryParams.addAll(_queryParams('{{{collectionFormat}}}', '{{{baseName}}}', {{{paramName}}}));
__queryParams.addAll(_queryParams('{{{collectionFormat}}}', '{{{baseName}}}', {{{paramName}}}));
{{^required}}
}
{{/required}}
Expand All @@ -76,58 +76,58 @@ class {{{classname}}} {

{{#headerParams}}
{{#required}}
headerParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
_headerParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
{{/required}}
{{^required}}
if ({{{paramName}}} != null) {
headerParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
_headerParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
}
{{/required}}
{{/headerParams}}
{{/hasHeaderParams}}

const contentTypes = <String>[{{#prioritizedContentTypes}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/prioritizedContentTypes}}];
const _contentTypes = <String>[{{#prioritizedContentTypes}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/prioritizedContentTypes}}];

{{#isMultipart}}
bool hasFields = false;
final mp = MultipartRequest('{{{httpMethod}}}', Uri.parse(path));
bool _hasFields = false;
final _mp = MultipartRequest('{{{httpMethod}}}', Uri.parse(_path));
{{#formParams}}
{{^isFile}}
if ({{{paramName}}} != null) {
hasFields = true;
mp.fields[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
_hasFields = true;
_mp.fields[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
}
{{/isFile}}
{{#isFile}}
if ({{{paramName}}} != null) {
hasFields = true;
mp.fields[r'{{{baseName}}}'] = {{{paramName}}}.field;
mp.files.add({{{paramName}}});
_hasFields = true;
_mp.fields[r'{{{baseName}}}'] = {{{paramName}}}.field;
_mp.files.add({{{paramName}}});
}
{{/isFile}}
{{/formParams}}
if (hasFields) {
postBody = mp;
if (_hasFields) {
_postBody = _mp;
}
{{/isMultipart}}
{{^isMultipart}}
{{#formParams}}
{{^isFile}}
if ({{{paramName}}} != null) {
formParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
_formParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
}
{{/isFile}}
{{/formParams}}
{{/isMultipart}}

return apiClient.invokeAPI(
path,
_path,
'{{{httpMethod}}}',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
__queryParams,
_postBody,
_headerParams,
_formParams,
_contentTypes.isEmpty ? null : _contentTypes.first,
);
}

Expand Down Expand Up @@ -162,28 +162,28 @@ class {{{classname}}} {
{{/-last}}
{{/allParams}}
Future<{{#returnType}}{{{.}}}?{{/returnType}}{{^returnType}}void{{/returnType}}> {{{nickname}}}({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async {
final response = await {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}} {{#allParams}}{{^required}}{{{paramName}}}: {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} {{/hasOptionalParams}});
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
final _response = await {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}} {{#allParams}}{{^required}}{{{paramName}}}: {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} {{/hasOptionalParams}});
if (_response.statusCode >= HttpStatus.badRequest) {
throw ApiException(_response.statusCode, await _decodeBodyBytes(_response));
}
{{#returnType}}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
if (_response.bodyBytes.isNotEmpty && _response.statusCode != HttpStatus.noContent) {
{{#native_serialization}}
{{#isArray}}
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, '{{{returnType}}}') as List)
final _responseBody = await _decodeBodyBytes(_response);
return (await apiClient.deserializeAsync(_responseBody, '{{{returnType}}}') as List)
.cast<{{{returnBaseType}}}>()
.{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}};
{{/isArray}}
{{^isArray}}
{{#isMap}}
return {{{returnType}}}.from(await apiClient.deserializeAsync(await _decodeBodyBytes(response), '{{{returnType}}}'),);
return {{{returnType}}}.from(await apiClient.deserializeAsync(await _decodeBodyBytes(_response), '{{{returnType}}}'),);
{{/isMap}}
{{^isMap}}
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), '{{{returnType}}}',) as {{{returnType}}};
return await apiClient.deserializeAsync(await _decodeBodyBytes(_response), '{{{returnType}}}',) as {{{returnType}}};
{{/isMap}}{{/isArray}}{{/native_serialization}}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,19 +134,21 @@ class ApiClient {
}
{{#native_serialization}}

Future<dynamic> deserializeAsync(String value, String targetType, {bool growable = false,}) async =>
Future<dynamic> deserializeAsync(dynamic value, String targetType, {bool growable = false,}) async =>
// ignore: deprecated_member_use_from_same_package
deserialize(value, targetType, growable: growable);

@Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use deserializeAsync() instead.')
dynamic deserialize(String value, String targetType, {bool growable = false,}) {
dynamic deserialize(dynamic value, String targetType, {bool growable = false,}) {
// Remove all spaces. Necessary for regular expressions as well.
targetType = targetType.replaceAll(' ', ''); // ignore: parameter_assignments

// If the expected target type is String, nothing to do...
return targetType == 'String'
? value
: fromJson(json.decode(value), targetType, growable: growable);
// If targetType is a known primitive, utilize it directly
if (targetType == 'String' || targetType == 'int' || targetType == 'double' || targetType == 'bool' || targetType == 'DateTime' || targetType == 'Uint8List') {
return fromJson(value, targetType, growable: growable);
} else {
return fromJson(json.decode(value), targetType, growable: growable);
}
}
{{/native_serialization}}

Expand Down Expand Up @@ -175,6 +177,8 @@ class ApiClient {
return valueString == 'true' || valueString == '1';
case 'DateTime':
return value is DateTime ? value : DateTime.tryParse(value);
case 'Uint8List':
return value as Uint8List;
{{#models}}
{{#model}}
case '{{{classname}}}':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,26 @@ String parameterToString(dynamic value) {
return value.toString();
}

/// Returns the decoded body as UTF-8 if the given headers indicate an 'application/json'
/// content type. Otherwise, returns the decoded body as decoded by dart:http package.
Future<String> _decodeBodyBytes(Response response) async {
final contentType = response.headers['content-type'];
return contentType != null && contentType.toLowerCase().startsWith('application/json')
? response.bodyBytes.isEmpty ? '' : utf8.decode(response.bodyBytes)
: response.body;
/// Decodes the body based on the content-type
/// application/json and text/plain are decoded as utf-8
/// all other types will be returned as Uint8List
Future<dynamic> _decodeBodyBytes(Response response) async {
final contentType = response.headers['content-type']?.toLowerCase();
if (contentType != null) {
if (contentType.startsWith('application/json') || contentType.startsWith('text/plain')) {
return response.bodyBytes.isEmpty ? '' : utf8.decode(response.bodyBytes);
}
}
/* all other responses should be returned as a raw Uint8List */
return response.bodyBytes;
}

/// Returns a valid [T] value found at the specified Map [key], null otherwise.
T? mapValueOfType<T>(dynamic map, String key) {
final dynamic value = map is Map ? map[key] : null;
if (T == double && value is int) {
return value.toDouble() as T;
}
return value is T ? value : null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:collection/collection.dart';
import 'package:http/http.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
A field is required in Dart when it is
required && !defaultValue in OAS
}}
{{#required}}{{^defaultValue}}required {{/defaultValue}}{{/required}}this.{{{name}}}{{#defaultValue}} = {{#isEnum}}{{^isContainer}}const {{{enumName}}}._({{/isContainer}}{{/isEnum}}{{{.}}}{{#isEnum}}{{^isContainer}}){{/isContainer}}{{/isEnum}}{{/defaultValue}},
{{#required}}{{^defaultValue}} required{{/defaultValue}}{{/required}} this.{{{name}}}{{#defaultValue}} = {{#isEnum}}{{^isContainer}}{{^isNumeric}}const {{{enumName}}}._({{/isNumeric}}{{/isContainer}}{{/isEnum}}{{{.}}}{{#isEnum}}{{^isContainer}}{{^isNumeric}}){{/isNumeric}}{{/isContainer}}{{/isEnum}}{{/defaultValue}},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds an extra white space which is not needed and now indented with 3 spaces.

{{/vars}}
});
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ class {{{classname}}} {
{{{name}}}: mapValueOfType<{{{datatypeWithEnum}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
{{/isEnum}}
{{#isEnum}}
{{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
{{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{#isEnum}}{{^isContainer}}const {{{enumName}}}._({{/isContainer}}{{/isEnum}}{{{.}}}{{#isEnum}}{{^isContainer}}){{/isContainer}}{{/isEnum}}{{/defaultValue}}{{/required}},
{{/isEnum}}
{{/isNumber}}
{{/isMap}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ class {{{classname}}} {
}
return result.toList(growable: growable);
}


@override
bool operator ==(Object other) => identical(this, other) || other is {{{classname}}} && other.value == value;

@override
int get hashCode => value.hashCode;
}

/// Transformation class that can [encode] an instance of [{{{classname}}}] to {{{dataType}}},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ class {{{enumName}}} {
}
return result.toList(growable: growable);
}

@override
bool operator ==(Object other) => identical(this, other) || other is {{{enumName}}} && other.value == value;

@override
int get hashCode => value.hashCode;
}

/// Transformation class that can [encode] an instance of [{{{enumName}}}] to {{{dataType}}},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ library openapi.api;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:collection/collection.dart';
import 'package:http/http.dart';
Expand Down
Loading