Skip to content

Commit c873a52

Browse files
authored
Merge pull request #3325 from inc-cloitsch/rmCrypto_fixRequiredParams_useAsyncAwait_addMultiQueryParams
dart: fix required params. add 'multi' query params. better deserialization.
2 parents 394840e + 8003156 commit c873a52

30 files changed

+1157
-664
lines changed

bin/dart-petstore.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,7 @@ fi
2828
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
2929
ags="$@ generate -i modules/swagger-codegen/src/test/resources/2_0/petstore.yaml -l dart -o samples/client/petstore/dart"
3030

31+
# for dart vm lib generation:
32+
#ags="$@ generate -i modules/swagger-codegen/src/test/resources/2_0/petstore.yaml -l dart -o samples/client/petstore/dart --additional-properties browserClient=false"
33+
3134
java $JAVA_OPTS -jar $executable $ags

modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/DartClientCodegen.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ public void processOpts() {
141141
final String libFolder = sourceFolder + File.separator + "lib";
142142
supportingFiles.add(new SupportingFile("pubspec.mustache", "", "pubspec.yaml"));
143143
supportingFiles.add(new SupportingFile("api_client.mustache", libFolder, "api_client.dart"));
144-
supportingFiles.add(new SupportingFile("apiException.mustache", libFolder, "api_exception.dart"));
144+
supportingFiles.add(new SupportingFile("api_exception.mustache", libFolder, "api_exception.dart"));
145+
supportingFiles.add(new SupportingFile("api_helper.mustache", libFolder, "api_helper.dart"));
145146
supportingFiles.add(new SupportingFile("apilib.mustache", libFolder, "api.dart"));
146147

147148
final String authFolder = sourceFolder + File.separator + "lib" + File.separator + "auth";

modules/swagger-codegen/src/main/resources/dart/api.mustache

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,44 @@
1-
part of api;
1+
part of {{pubName}}.api;
22

33
{{#operations}}
44

5+
56
class {{classname}} {
6-
String basePath = "{{basePath}}";
7-
ApiClient apiClient = ApiClient.defaultApiClient;
7+
final ApiClient apiClient;
88
9-
{{classname}}([ApiClient apiClient]) {
10-
if (apiClient != null) {
11-
this.apiClient = apiClient;
12-
}
13-
}
9+
{{classname}}([ApiClient apiClient]) : apiClient = apiClient ?? defaultApiClient;
1410

1511
{{#operation}}
1612
/// {{summary}}
1713
///
1814
/// {{notes}}
19-
{{#returnType}}Future<{{{returnType}}}> {{/returnType}}{{^returnType}}Future {{/returnType}}{{nickname}}({{#allParams}}{{{dataType}}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) {
15+
{{#returnType}}Future<{{{returnType}}}> {{/returnType}}{{^returnType}}Future {{/returnType}}{{nickname}}({{#allParams}}{{#required}}{{{dataType}}} {{paramName}}, {{/required}}{{/allParams}} { {{#allParams}}{{^required}} {{{dataType}}} {{paramName}}, {{/required}}{{/allParams}} bool justIgnoreThisFlag: true}) async {
16+
if (!justIgnoreThisFlag) {
17+
print('Why??? Just trust me, I only need this variable inside the mustache codegen template.');
18+
// This code may be removed as soon as dart accepts trailing spaces (has already been implemented).
19+
}
2020
Object postBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}};
21-
{{#allParams}}
21+
2222
// verify required params are set
23-
if({{/allParams}}{{#required}} {{paramName}} == null {{#hasMore}}|| {{/hasMore}}{{/required}}{{#allParams}}) {
24-
throw new ApiException(400, "missing required params");
25-
}{{/allParams}}
23+
{{#allParams}}
24+
{{#required}}
25+
if({{paramName}} == null) {
26+
throw new ApiException(400, "Missing required param: {{paramName}}");
27+
}
28+
{{/required}}
29+
{{/allParams}}
2630

2731
// create path and map variables
2832
String path = "{{path}}".replaceAll("{format}","json"){{#pathParams}}.replaceAll("{" + "{{paramName}}" + "}", {{{paramName}}}.toString()){{/pathParams}};
2933

3034
// query params
31-
Map<String, String> queryParams = {};
35+
List<QueryParam> queryParams = [];
3236
Map<String, String> headerParams = {};
3337
Map<String, String> formParams = {};
34-
{{#queryParams}}if("null" != {{paramName}})
35-
queryParams["{{baseName}}"] = {{paramName}} is List ? {{paramName}}.join(',') : {{paramName}};
38+
{{#queryParams}}
39+
if("null" != {{paramName}}) {
40+
queryParams.addAll(_convertParametersForCollectionFormat("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
41+
}
3642
{{/queryParams}}
3743
{{#headerParams}}headerParams["{{baseName}}"] = {{paramName}};
3844
{{/headerParams}}
@@ -66,17 +72,22 @@ class {{classname}} {
6672
{{/formParams}}
6773
}
6874

69-
return apiClient.invokeAPI(basePath, path, '{{httpMethod}}', queryParams, postBody, headerParams, formParams, contentType, authNames).then((response) {
70-
if(response.statusCode >= 400) {
71-
throw new ApiException(response.statusCode, response.body);
72-
}
73-
else if(response.body != null){
74-
return {{#returnType}}ApiClient.deserialize(response.body, {{returnBaseType}}){{/returnType}};
75-
}
76-
else {
77-
return {{#returnType}}null{{/returnType}};
78-
}
79-
});
75+
var response = await apiClient.invokeAPI(path,
76+
'{{httpMethod}}',
77+
queryParams,
78+
postBody,
79+
headerParams,
80+
formParams,
81+
contentType,
82+
authNames);
83+
84+
if(response.statusCode >= 400) {
85+
throw new ApiException(response.statusCode, response.body);
86+
} else if(response.body != null) {
87+
return {{#returnType}} apiClient.deserialize(response.body, '{{{returnType}}}') {{/returnType}};
88+
} else {
89+
return {{#returnType}}null{{/returnType}};
90+
}
8091
}
8192
{{/operation}}
8293
}
Lines changed: 84 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
1-
part of api;
1+
part of {{pubName}}.api;
2+
3+
class QueryParam {
4+
String name;
5+
String value;
6+
7+
QueryParam(this.name, this.value);
8+
}
29

310
class ApiClient {
4-
static ApiClient defaultApiClient = new ApiClient();
11+
12+
String basePath;
13+
var client = new {{#browserClient}}Browser{{/browserClient}}Client();
514

615
Map<String, String> _defaultHeaderMap = {};
716
Map<String, Authentication> _authentications = {};
8-
static final dson = new Dartson.JSON();
17+
18+
final dson = new Dartson.JSON();
919
final DateFormat _dateFormatter = new DateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
1020

11-
ApiClient() {
21+
final _RegList = new RegExp(r'^List<(.*)>$');
22+
final _RegMap = new RegExp(r'^Map<String,(.*)>$');
23+
24+
ApiClient({this.basePath: "{{{basePath}}}"}) {
1225
// Setup authentications (key: authentication name, value: authentication).{{#authMethods}}{{#isBasic}}
1326
_authentications['{{name}}'] = new HttpBasicAuth();{{/isBasic}}{{#isApiKey}}
1427
_authentications['{{name}}'] = new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{^isKeyInHeader}}"query"{{/isKeyInHeader}}, "{{keyParamName}}");{{/isApiKey}}{{#isOAuth}}
@@ -37,43 +50,57 @@ class ApiClient {
3750
}
3851
}
3952

40-
static dynamic deserialize(String json, dynamic clazz) {
41-
var result = json;
42-
53+
dynamic _deserialize(dynamic value, String targetType) {
4354
try {
44-
var decodedJson = JSON.decode(json);
45-
46-
if(decodedJson is List) {
47-
result = [];
48-
for(var obj in decodedJson) {
49-
result.add(_createEntity(obj, clazz));
50-
}
51-
} else {
52-
result = _createEntity(json, clazz);
55+
switch (targetType) {
56+
case 'String':
57+
return '$value';
58+
case 'int':
59+
return value is int ? value : int.parse('$value');
60+
case 'bool':
61+
return value is bool ? value : '$value'.toLowerCase() == 'true';
62+
case 'double':
63+
return value is double ? value : double.parse('$value');
64+
{{#models}}
65+
{{#model}}
66+
case '{{classname}}':
67+
return dson.map(value, new {{classname}}());
68+
{{/model}}
69+
{{/models}}
70+
default:
71+
{
72+
Match match;
73+
if (value is List &&
74+
(match = _RegList.firstMatch(targetType)) != null) {
75+
var valueL = value as List;
76+
var newTargetType = match[1];
77+
return valueL.map((v) => _deserialize(v, newTargetType)).toList();
78+
} else if (value is Map &&
79+
(match = _RegMap.firstMatch(targetType)) != null) {
80+
var valueM = value as Map;
81+
var newTargetType = match[1];
82+
return new Map.fromIterables(valueM.keys,
83+
valueM.values.map((v) => _deserialize(v, newTargetType)));
84+
}
85+
}
5386
}
54-
} on FormatException {
55-
// Just return the passed in value
87+
} catch(e) {
88+
// Just throw the ApiException below
5689
}
57-
58-
return result;
90+
throw new ApiException(500, 'Could not find a suitable class for deserialization');
5991
}
6092

61-
static dynamic _createEntity(dynamic json, dynamic clazz) {
62-
bool isMap = json is Map;
63-
64-
switch(clazz) {
65-
{{#models}}
66-
{{#model}}
67-
case {{classname}}:
68-
return isMap ? dson.map(json, new {{classname}}()) : dson.decode(json, new {{classname}}());
69-
{{/model}}
70-
{{/models}}
71-
default:
72-
throw new ApiException(500, 'Could not find a suitable class for deserialization');
73-
}
93+
dynamic deserialize(String json, String targetType) {
94+
// Remove all spaces. Necessary for reg expressions as well.
95+
targetType = targetType.replaceAll(' ', '');
96+
97+
if (targetType == 'String') return json;
98+
99+
var decodedJson = JSON.decode(json);
100+
return _deserialize(decodedJson, targetType);
74101
}
75102

76-
static String serialize(Object obj) {
103+
String serialize(Object obj) {
77104
String serialized = '';
78105
if (obj == null) {
79106
serialized = '';
@@ -85,76 +112,59 @@ class ApiClient {
85112
return serialized;
86113
}
87114

88-
Future<Response> invokeAPI( String host,
89-
String path,
90-
String method,
91-
Map<String, String> queryParams,
92-
Object body,
93-
Map<String, String> headerParams,
94-
Map<String, String> formParams,
95-
String contentType,
96-
List<String> authNames) {
97-
98-
updateParamsForAuth(authNames, queryParams, headerParams);
99-
100-
var client = new {{#browserClient}}Browser{{/browserClient}}Client();
101-
102-
StringBuffer sb = new StringBuffer();
103-
104-
for(String key in queryParams.keys) {
105-
String value = queryParams[key];
106-
if (value != null){
107-
if(sb.toString().length == 0) {
108-
sb.write("?");
109-
} else {
110-
sb.write("&");
111-
}
112-
sb.write(key);
113-
sb.write("=");
114-
sb.write(value);
115-
}
116-
}
117-
String querystring = sb.toString();
115+
// We don't use a Map<String, String> for queryParams.
116+
// If collectionFormat is 'multi' a key might appear multiple times.
117+
Future<Response> invokeAPI(String path,
118+
String method,
119+
List<QueryParam> queryParams,
120+
Object body,
121+
Map<String, String> headerParams,
122+
Map<String, String> formParams,
123+
String contentType,
124+
List<String> authNames) async {
125+
126+
_updateParamsForAuth(authNames, queryParams, headerParams);
127+
128+
var ps = queryParams.where((p) => p.value != null).map((p) => '${p.name}=${p.value}');
129+
String queryString = ps.isNotEmpty ?
130+
'?' + ps.join('&') :
131+
'';
118132
119-
String url = host + path + querystring;
133+
String url = basePath + path + queryString;
120134
121135
headerParams.addAll(_defaultHeaderMap);
122136
headerParams['Content-Type'] = contentType;
123137
124-
var completer = new Completer();
125-
126138
if(body is MultipartRequest) {
127139
var request = new MultipartRequest(method, Uri.parse(url));
128140
request.fields.addAll(body.fields);
129141
request.files.addAll(body.files);
130142
request.headers.addAll(body.headers);
131143
request.headers.addAll(headerParams);
132-
client.send(request).then((response) => completer.complete(Response.fromStream(response)));
144+
var response = await client.send(request);
145+
return Response.fromStream(response);
133146
} else {
134147
var msgBody = contentType == "application/x-www-form-urlencoded" ? formParams : serialize(body);
135148
switch(method) {
136-
case "GET":
137-
return client.get(url, headers: headerParams);
138149
case "POST":
139150
return client.post(url, headers: headerParams, body: msgBody);
140151
case "PUT":
141152
return client.put(url, headers: headerParams, body: msgBody);
142153
case "DELETE":
143154
return client.delete(url, headers: headerParams);
155+
default:
156+
return client.get(url, headers: headerParams);
144157
}
145158
}
146-
147-
return completer.future;
148159
}
149160

150161
/// Update query and header parameters based on authentication settings.
151162
/// @param authNames The authentications to apply
152-
void updateParamsForAuth(List<String> authNames, Map<String, String> queryParams, Map<String, String> headerParams) {
163+
void _updateParamsForAuth(List<String> authNames, List<QueryParam> queryParams, Map<String, String> headerParams) {
153164
authNames.forEach((authName) {
154165
Authentication auth = _authentications[authName];
155166
if (auth == null) throw new ArgumentError("Authentication undefined: " + authName);
156167
auth.applyToParams(queryParams, headerParams);
157168
});
158169
}
159-
160170
}

modules/swagger-codegen/src/main/resources/dart/apiException.mustache renamed to modules/swagger-codegen/src/main/resources/dart/api_exception.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
part of api;
1+
part of {{pubName}}.api;
22

33
class ApiException implements Exception {
44
int code = 0;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
part of {{pubName}}.api;
2+
3+
const _delimiters = const {'csv': ',', 'ssv': ' ', 'tsv': '\t', 'pipes': '|'};
4+
5+
// port from Java version
6+
List<QueryParam> _convertParametersForCollectionFormat(
7+
String collectionFormat, String name, dynamic value) {
8+
var params = [];
9+
10+
// preconditions
11+
if (name == null || name.isEmpty || value == null) return params;
12+
13+
if (value is! List) {
14+
params.add(new QueryParam(name, value as String));
15+
return params;
16+
}
17+
18+
List<String> values = value as List<String>;
19+
20+
// get the collection format
21+
collectionFormat = (collectionFormat == null || collectionFormat.isEmpty)
22+
? "csv"
23+
: collectionFormat; // default: csv
24+
25+
if (collectionFormat == "multi") {
26+
return values.map((v) => new QueryParam(name, v));
27+
}
28+
29+
String delimiter = _delimiters[collectionFormat] ?? ",";
30+
31+
params.add(new QueryParam(name, values.join(delimiter)));
32+
return params;
33+
}
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
library api;
1+
library {{pubName}}.api;
22

33
import 'dart:async';
44
import 'dart:convert';{{#browserClient}}
5-
import 'dart:html';
65
import 'package:http/browser_client.dart';{{/browserClient}}
76
import 'package:http/http.dart';
87
import 'package:dartson/dartson.dart';
9-
import 'package:crypto/crypto.dart';
108
import 'package:intl/intl.dart';
119

1210
part 'api_client.dart';
11+
part 'api_helper.dart';
1312
part 'api_exception.dart';
1413
part 'auth/authentication.dart';
1514
part 'auth/api_key_auth.dart';
@@ -20,3 +19,6 @@ part 'auth/http_basic_auth.dart';
2019
{{/apis}}{{/apiInfo}}
2120
{{#models}}{{#model}}part 'model/{{classFilename}}.dart';
2221
{{/model}}{{/models}}
22+
23+
ApiClient defaultApiClient = new ApiClient();
24+

0 commit comments

Comments
 (0)