[kotlin] support collection format multi (#5792)

* [kotlin] support collectionFormat:multi

Adds "multi" support to collections.

Also changes generic lists (List<T>) to arrays. Generic lists and nested
lists can be problematic and require customized json factories. The
previous implement appeared to work because the results in the test were
LinkedHashMap with count greather than 0. The functional test has been
updated to force serialization and verify the results.

* [kotlin] Regenerate sample

* [kotlin] Update model test for Array changes
This commit is contained in:
Jim Schubert
2017-06-09 03:40:18 -04:00
committed by wing328
parent 2d008be181
commit dabf01c3fa
17 changed files with 120 additions and 92 deletions

View File

@@ -125,16 +125,16 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
typeMapping.put("date-time", "java.time.LocalDateTime");
typeMapping.put("date", "java.time.LocalDateTime");
typeMapping.put("file", "java.io.File");
typeMapping.put("array", "kotlin.collections.List");
typeMapping.put("list", "kotlin.collections.List");
typeMapping.put("array", "kotlin.Array");
typeMapping.put("list", "kotlin.Array");
typeMapping.put("map", "kotlin.collections.Map");
typeMapping.put("object", "kotlin.Any");
typeMapping.put("binary", "kotlin.Array<kotlin.Byte>");
typeMapping.put("Date", "java.time.LocalDateTime");
typeMapping.put("DateTime", "java.time.LocalDateTime");
instantiationTypes.put("array", "listOf");
instantiationTypes.put("list", "listOf");
instantiationTypes.put("array", "arrayOf");
instantiationTypes.put("list", "arrayOf");
instantiationTypes.put("map", "mapOf");
importMapping = new HashMap<String, String>();
@@ -241,6 +241,7 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", "/");
supportingFiles.add(new SupportingFile("infrastructure/ApiClient.kt.mustache", infrastructureFolder, "ApiClient.kt"));
supportingFiles.add(new SupportingFile("infrastructure/ApiAbstractions.kt.mustache", infrastructureFolder, "ApiAbstractions.kt"));
supportingFiles.add(new SupportingFile("infrastructure/ApiInfrastructureResponse.kt.mustache", infrastructureFolder, "ApiInfrastructureResponse.kt"));
supportingFiles.add(new SupportingFile("infrastructure/ApplicationDelegates.kt.mustache", infrastructureFolder, "ApplicationDelegates.kt"));
supportingFiles.add(new SupportingFile("infrastructure/RequestConfig.kt.mustache", infrastructureFolder, "RequestConfig.kt"));

View File

@@ -19,7 +19,7 @@ class {{classname}}(basePath: kotlin.String = "{{{basePath}}}") : ApiClient(base
@Suppress("UNCHECKED_CAST"){{/returnType}}
fun {{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) : {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}} {
val localVariableBody: kotlin.Any? = {{#hasBodyParam}}{{#bodyParams}}{{paramName}}{{/bodyParams}}{{/hasBodyParam}}{{^hasBodyParam}}{{^hasFormParams}}null{{/hasFormParams}}{{#hasFormParams}}mapOf({{#formParams}}"{{{baseName}}}" to "${{paramName}}"{{#hasMore}}, {{/hasMore}}{{/formParams}}){{/hasFormParams}}{{/hasBodyParam}}
val localVariableQuery: kotlin.collections.Map<kotlin.String,kotlin.String> = {{^hasQueryParams}}mapOf(){{/hasQueryParams}}{{#hasQueryParams}}mapOf({{#queryParams}}"{{paramName}}" to {{#isContainer}}{{paramName}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}{{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/queryParams}}){{/hasQueryParams}}
val localVariableQuery: MultiValueMap = {{^hasQueryParams}}mapOf(){{/hasQueryParams}}{{#hasQueryParams}}mapOf({{#queryParams}}"{{paramName}}" to {{#isContainer}}toMultiValue({{paramName}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf("${{paramName}}"){{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/queryParams}}){{/hasQueryParams}}
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = {{^headerParams}}mapOf({{#hasFormParams}}"Content-Type" to "multipart/form-data"{{/hasFormParams}}){{/headerParams}}{{#headerParams}}mapOf("{{paramName}}" to {{#isContainer}}{{paramName}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}{{paramName}}{{/isContainer}}){{/headerParams}}
val localVariableConfig = RequestConfig(
RequestMethod.{{httpMethod}},
@@ -43,13 +43,5 @@ class {{classname}}(basePath: kotlin.String = "{{{basePath}}}") : ApiClient(base
}
{{/operation}}
private fun collectionDelimiter(collectionFormat: kotlin.String) = when(collectionFormat) {
"csv" -> ","
"tsv" -> "\t"
"pipes" -> "|"
"ssv" -> " "
else -> ""
}
}
{{/operations}}

View File

@@ -0,0 +1,20 @@
package {{packageName}}.infrastructure
typealias MultiValueMap = Map<String,List<String>>
fun collectionDelimiter(collectionFormat: String) = when(collectionFormat) {
"csv" -> ","
"tsv" -> "\t"
"pipes" -> "|"
"ssv" -> " "
else -> ""
}
val defaultMultiValueConverter: (item: Any?) -> String = { item -> "$item" }
fun <T: Any?> toMultiValue(items: List<T>, collectionFormat: String, map: (item: Any?) -> String = defaultMultiValueConverter): List<String> {
return when(collectionFormat) {
"multi" -> items.map(map)
else -> listOf(items.map(map).joinToString(separator = collectionDelimiter(collectionFormat)))
}
}

View File

@@ -60,7 +60,11 @@ open class ApiClient(val baseUrl: String) {
var urlBuilder = httpUrl.newBuilder()
.addPathSegments(requestConfig.path.trimStart('/'))
requestConfig.query.forEach { k, v -> urlBuilder = urlBuilder.addQueryParameter(k,v) }
requestConfig.query.forEach { k, v ->
v.forEach { queryValue ->
urlBuilder = urlBuilder.addQueryParameter(k,queryValue)
}
}
val url = urlBuilder.build()
val headers = requestConfig.headers + defaultHeaders
@@ -73,6 +77,7 @@ open class ApiClient(val baseUrl: String) {
throw kotlin.IllegalStateException("Missing Accept header. This is required.")
}
// TODO: support multiple contentType,accept options here.
val contentType = (headers[ContentType] as String).substringBefore(";").toLowerCase()
val accept = (headers[Accept] as String).substringBefore(";").toLowerCase()

View File

@@ -5,9 +5,11 @@ package {{packageName}}.infrastructure
* NOTE: This object doesn't include 'body' because it
* allows for caching of the constructed object
* for many request definitions.
* NOTE: Headers is a Map<String,String> because rfc2616 defines
* multi-valued headers as csv-only.
*/
data class RequestConfig(
val method: RequestMethod,
val path: String,
val headers: Map<String, String> = mapOf(),
val query: Map<String, String> = mapOf())
val query: Map<String, List<String>> = mapOf())

View File

@@ -104,10 +104,10 @@ public class KotlinClientCodegenModelTest {
Assert.assertEquals(property.baseName, "examples");
Assert.assertEquals(property.getter, "getExamples");
Assert.assertEquals(property.setter, "setExamples");
Assert.assertEquals(property.datatype, "kotlin.collections.List<kotlin.String>");
Assert.assertEquals(property.datatype, "kotlin.Array<kotlin.String>");
Assert.assertEquals(property.name, "examples");
Assert.assertEquals(property.defaultValue, "null");
Assert.assertEquals(property.baseType, "kotlin.collections.List");
Assert.assertEquals(property.baseType, "kotlin.Array");
Assert.assertEquals(property.containerType, "array");
Assert.assertFalse(property.required);
Assert.assertTrue(property.isContainer);