Skip to content
Merged
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
3 changes: 3 additions & 0 deletions bin/dart-petstore.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,7 @@ fi
export JAVA_OPTS="${JAVA_OPTS} -XX:MaxPermSize=256M -Xmx1024M -DloggerPath=conf/log4j.properties"
ags="$@ generate -i modules/swagger-codegen/src/test/resources/2_0/petstore.yaml -l dart -o samples/client/petstore/dart"

# for dart vm lib generation:
#ags="$@ generate -i modules/swagger-codegen/src/test/resources/2_0/petstore.yaml -l dart -o samples/client/petstore/dart --additional-properties browserClient=false"

Copy link
Contributor

Choose a reason for hiding this comment

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

You may want to create another dart petstore shell script (e.g. dart-vmlib-petstore.sh) for the --additional-properties browserClient=false option (similar to what we've done for other langauges, e.g. java)

Copy link
Contributor

Choose a reason for hiding this comment

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

And you may consider later adding a CLI option (e.g. dartBrowserClient) instead of using additional properties.

java $JAVA_OPTS -jar $executable $ags
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ public void processOpts() {
final String libFolder = sourceFolder + File.separator + "lib";
supportingFiles.add(new SupportingFile("pubspec.mustache", "", "pubspec.yaml"));
supportingFiles.add(new SupportingFile("api_client.mustache", libFolder, "api_client.dart"));
supportingFiles.add(new SupportingFile("apiException.mustache", libFolder, "api_exception.dart"));
supportingFiles.add(new SupportingFile("api_exception.mustache", libFolder, "api_exception.dart"));
supportingFiles.add(new SupportingFile("api_helper.mustache", libFolder, "api_helper.dart"));
supportingFiles.add(new SupportingFile("apilib.mustache", libFolder, "api.dart"));

final String authFolder = sourceFolder + File.separator + "lib" + File.separator + "auth";
Expand Down
65 changes: 38 additions & 27 deletions modules/swagger-codegen/src/main/resources/dart/api.mustache
Original file line number Diff line number Diff line change
@@ -1,38 +1,44 @@
part of api;
part of {{pubName}}.api;

{{#operations}}


class {{classname}} {
String basePath = "{{basePath}}";
ApiClient apiClient = ApiClient.defaultApiClient;
final ApiClient apiClient;

{{classname}}([ApiClient apiClient]) {
if (apiClient != null) {
this.apiClient = apiClient;
}
}
{{classname}}([ApiClient apiClient]) : apiClient = apiClient ?? defaultApiClient;

{{#operation}}
/// {{summary}}
///
/// {{notes}}
{{#returnType}}Future<{{{returnType}}}> {{/returnType}}{{^returnType}}Future {{/returnType}}{{nickname}}({{#allParams}}{{{dataType}}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) {
{{#returnType}}Future<{{{returnType}}}> {{/returnType}}{{^returnType}}Future {{/returnType}}{{nickname}}({{#allParams}}{{#required}}{{{dataType}}} {{paramName}}, {{/required}}{{/allParams}} { {{#allParams}}{{^required}} {{{dataType}}} {{paramName}}, {{/required}}{{/allParams}} bool justIgnoreThisFlag: true}) async {
if (!justIgnoreThisFlag) {
print('Why??? Just trust me, I only need this variable inside the mustache codegen template.');
// This code may be removed as soon as dart accepts trailing spaces (has already been implemented).
}
Object postBody = {{#bodyParam}}{{paramName}}{{/bodyParam}}{{^bodyParam}}null{{/bodyParam}};
{{#allParams}}

// verify required params are set
if({{/allParams}}{{#required}} {{paramName}} == null {{#hasMore}}|| {{/hasMore}}{{/required}}{{#allParams}}) {
throw new ApiException(400, "missing required params");
}{{/allParams}}
{{#allParams}}
{{#required}}
if({{paramName}} == null) {
throw new ApiException(400, "Missing required param: {{paramName}}");
}
{{/required}}
{{/allParams}}

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

// query params
Map<String, String> queryParams = {};
List<QueryParam> queryParams = [];
Map<String, String> headerParams = {};
Map<String, String> formParams = {};
{{#queryParams}}if("null" != {{paramName}})
queryParams["{{baseName}}"] = {{paramName}} is List ? {{paramName}}.join(',') : {{paramName}};
{{#queryParams}}
if("null" != {{paramName}}) {
queryParams.addAll(_convertParametersForCollectionFormat("{{collectionFormat}}", "{{baseName}}", {{paramName}}));
}
{{/queryParams}}
{{#headerParams}}headerParams["{{baseName}}"] = {{paramName}};
{{/headerParams}}
Expand Down Expand Up @@ -66,17 +72,22 @@ class {{classname}} {
{{/formParams}}
}

return apiClient.invokeAPI(basePath, path, '{{httpMethod}}', queryParams, postBody, headerParams, formParams, contentType, authNames).then((response) {
if(response.statusCode >= 400) {
throw new ApiException(response.statusCode, response.body);
}
else if(response.body != null){
return {{#returnType}}ApiClient.deserialize(response.body, {{returnBaseType}}){{/returnType}};
}
else {
return {{#returnType}}null{{/returnType}};
}
});
var response = await apiClient.invokeAPI(path,
'{{httpMethod}}',
queryParams,
postBody,
headerParams,
formParams,
contentType,
authNames);

if(response.statusCode >= 400) {
throw new ApiException(response.statusCode, response.body);
} else if(response.body != null) {
return {{#returnType}} apiClient.deserialize(response.body, '{{{returnType}}}') {{/returnType}};
} else {
return {{#returnType}}null{{/returnType}};
}
}
{{/operation}}
}
Expand Down
158 changes: 84 additions & 74 deletions modules/swagger-codegen/src/main/resources/dart/api_client.mustache
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
part of api;
part of {{pubName}}.api;

class QueryParam {
String name;
String value;

QueryParam(this.name, this.value);
}

class ApiClient {
static ApiClient defaultApiClient = new ApiClient();

String basePath;
var client = new {{#browserClient}}Browser{{/browserClient}}Client();

Map<String, String> _defaultHeaderMap = {};
Map<String, Authentication> _authentications = {};
static final dson = new Dartson.JSON();

final dson = new Dartson.JSON();
final DateFormat _dateFormatter = new DateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");

ApiClient() {
final _RegList = new RegExp(r'^List<(.*)>$');
final _RegMap = new RegExp(r'^Map<String,(.*)>$');

ApiClient({this.basePath: "{{{basePath}}}"}) {
// Setup authentications (key: authentication name, value: authentication).{{#authMethods}}{{#isBasic}}
_authentications['{{name}}'] = new HttpBasicAuth();{{/isBasic}}{{#isApiKey}}
_authentications['{{name}}'] = new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{^isKeyInHeader}}"query"{{/isKeyInHeader}}, "{{keyParamName}}");{{/isApiKey}}{{#isOAuth}}
Expand Down Expand Up @@ -37,43 +50,57 @@ class ApiClient {
}
}

static dynamic deserialize(String json, dynamic clazz) {
var result = json;

dynamic _deserialize(dynamic value, String targetType) {
try {
var decodedJson = JSON.decode(json);

if(decodedJson is List) {
result = [];
for(var obj in decodedJson) {
result.add(_createEntity(obj, clazz));
}
} else {
result = _createEntity(json, clazz);
switch (targetType) {
case 'String':
return '$value';
case 'int':
return value is int ? value : int.parse('$value');
case 'bool':
return value is bool ? value : '$value'.toLowerCase() == 'true';
case 'double':
return value is double ? value : double.parse('$value');
{{#models}}
{{#model}}
case '{{classname}}':
return dson.map(value, new {{classname}}());
{{/model}}
{{/models}}
default:
{
Match match;
if (value is List &&
(match = _RegList.firstMatch(targetType)) != null) {
var valueL = value as List;
var newTargetType = match[1];
return valueL.map((v) => _deserialize(v, newTargetType)).toList();
} else if (value is Map &&
(match = _RegMap.firstMatch(targetType)) != null) {
var valueM = value as Map;
var newTargetType = match[1];
return new Map.fromIterables(valueM.keys,
valueM.values.map((v) => _deserialize(v, newTargetType)));
}
}
}
} on FormatException {
// Just return the passed in value
} catch(e) {
// Just throw the ApiException below
}

return result;
throw new ApiException(500, 'Could not find a suitable class for deserialization');
}

static dynamic _createEntity(dynamic json, dynamic clazz) {
bool isMap = json is Map;

switch(clazz) {
{{#models}}
{{#model}}
case {{classname}}:
return isMap ? dson.map(json, new {{classname}}()) : dson.decode(json, new {{classname}}());
{{/model}}
{{/models}}
default:
throw new ApiException(500, 'Could not find a suitable class for deserialization');
}
dynamic deserialize(String json, String targetType) {
// Remove all spaces. Necessary for reg expressions as well.
targetType = targetType.replaceAll(' ', '');

if (targetType == 'String') return json;

var decodedJson = JSON.decode(json);
return _deserialize(decodedJson, targetType);
}

static String serialize(Object obj) {
String serialize(Object obj) {
String serialized = '';
if (obj == null) {
serialized = '';
Expand All @@ -85,76 +112,59 @@ class ApiClient {
return serialized;
}

Future<Response> invokeAPI( String host,
String path,
String method,
Map<String, String> queryParams,
Object body,
Map<String, String> headerParams,
Map<String, String> formParams,
String contentType,
List<String> authNames) {

updateParamsForAuth(authNames, queryParams, headerParams);

var client = new {{#browserClient}}Browser{{/browserClient}}Client();

StringBuffer sb = new StringBuffer();

for(String key in queryParams.keys) {
String value = queryParams[key];
if (value != null){
if(sb.toString().length == 0) {
sb.write("?");
} else {
sb.write("&");
}
sb.write(key);
sb.write("=");
sb.write(value);
}
}
String querystring = sb.toString();
// We don't use a Map<String, String> for queryParams.
// If collectionFormat is 'multi' a key might appear multiple times.
Future<Response> invokeAPI(String path,
String method,
List<QueryParam> queryParams,
Object body,
Map<String, String> headerParams,
Map<String, String> formParams,
String contentType,
List<String> authNames) async {

_updateParamsForAuth(authNames, queryParams, headerParams);

var ps = queryParams.where((p) => p.value != null).map((p) => '${p.name}=${p.value}');
String queryString = ps.isNotEmpty ?
'?' + ps.join('&') :
'';

String url = host + path + querystring;
String url = basePath + path + queryString;

headerParams.addAll(_defaultHeaderMap);
headerParams['Content-Type'] = contentType;

var completer = new Completer();

if(body is MultipartRequest) {
var request = new MultipartRequest(method, Uri.parse(url));
request.fields.addAll(body.fields);
request.files.addAll(body.files);
request.headers.addAll(body.headers);
request.headers.addAll(headerParams);
client.send(request).then((response) => completer.complete(Response.fromStream(response)));
var response = await client.send(request);
return Response.fromStream(response);
} else {
var msgBody = contentType == "application/x-www-form-urlencoded" ? formParams : serialize(body);
switch(method) {
case "GET":
return client.get(url, headers: headerParams);
case "POST":
return client.post(url, headers: headerParams, body: msgBody);
case "PUT":
return client.put(url, headers: headerParams, body: msgBody);
case "DELETE":
return client.delete(url, headers: headerParams);
default:
return client.get(url, headers: headerParams);
}
}

return completer.future;
}

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

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
part of api;
part of {{pubName}}.api;

class ApiException implements Exception {
int code = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
part of {{pubName}}.api;

const _delimiters = const {'csv': ',', 'ssv': ' ', 'tsv': '\t', 'pipes': '|'};

// port from Java version
List<QueryParam> _convertParametersForCollectionFormat(
String collectionFormat, String name, dynamic value) {
var params = [];

// preconditions
if (name == null || name.isEmpty || value == null) return params;

if (value is! List) {
params.add(new QueryParam(name, value as String));
return params;
}

List<String> values = value as List<String>;

// get the collection format
collectionFormat = (collectionFormat == null || collectionFormat.isEmpty)
? "csv"
: collectionFormat; // default: csv

if (collectionFormat == "multi") {
return values.map((v) => new QueryParam(name, v));
}

String delimiter = _delimiters[collectionFormat] ?? ",";

params.add(new QueryParam(name, values.join(delimiter)));
return params;
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
library api;
library {{pubName}}.api;

import 'dart:async';
import 'dart:convert';{{#browserClient}}
import 'dart:html';
import 'package:http/browser_client.dart';{{/browserClient}}
import 'package:http/http.dart';
import 'package:dartson/dartson.dart';
import 'package:crypto/crypto.dart';
import 'package:intl/intl.dart';

part 'api_client.dart';
part 'api_helper.dart';
part 'api_exception.dart';
part 'auth/authentication.dart';
part 'auth/api_key_auth.dart';
Expand All @@ -20,3 +19,6 @@ part 'auth/http_basic_auth.dart';
{{/apis}}{{/apiInfo}}
{{#models}}{{#model}}part 'model/{{classFilename}}.dart';
{{/model}}{{/models}}

ApiClient defaultApiClient = new ApiClient();

Loading