From dabf01c3fa457928858dd4074bfb46eba71b409e Mon Sep 17 00:00:00 2001 From: Jim Schubert Date: Fri, 9 Jun 2017 03:40:18 -0400 Subject: [PATCH] [kotlin] support collection format multi (#5792) * [kotlin] support collectionFormat:multi Adds "multi" support to collections. Also changes generic lists (List) 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 --- .../languages/KotlinClientCodegen.java | 9 +++-- .../main/resources/kotlin-client/api.mustache | 10 +---- .../ApiAbstractions.kt.mustache | 20 ++++++++++ .../infrastructure/ApiClient.kt.mustache | 7 +++- .../infrastructure/RequestConfig.kt.mustache | 4 +- .../kotlin/KotlinClientCodegenModelTest.java | 4 +- samples/client/petstore/kotlin/docs/Pet.md | 4 +- samples/client/petstore/kotlin/docs/PetApi.md | 20 +++++----- .../client/petstore/kotlin/docs/UserApi.md | 8 ++-- .../kotlin/io/swagger/client/apis/PetApi.kt | 40 ++++++++----------- .../kotlin/io/swagger/client/apis/StoreApi.kt | 16 ++------ .../kotlin/io/swagger/client/apis/UserApi.kt | 28 +++++-------- .../client/infrastructure/ApiAbstractions.kt | 20 ++++++++++ .../client/infrastructure/ApiClient.kt | 7 +++- .../client/infrastructure/RequestConfig.kt | 4 +- .../kotlin/io/swagger/client/models/Pet.kt | 4 +- .../swagger/client/functional/EvaluateTest.kt | 7 +++- 17 files changed, 120 insertions(+), 92 deletions(-) create mode 100644 modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/ApiAbstractions.kt.mustache create mode 100644 samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/infrastructure/ApiAbstractions.kt diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java index 7e52a1c655..2df436d195 100644 --- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java +++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/KotlinClientCodegen.java @@ -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"); 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(); @@ -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")); diff --git a/modules/swagger-codegen/src/main/resources/kotlin-client/api.mustache b/modules/swagger-codegen/src/main/resources/kotlin-client/api.mustache index 545cd29e69..cae4e8cb62 100644 --- a/modules/swagger-codegen/src/main/resources/kotlin-client/api.mustache +++ b/modules/swagger-codegen/src/main/resources/kotlin-client/api.mustache @@ -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 = {{^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 = {{^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}} \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/ApiAbstractions.kt.mustache b/modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/ApiAbstractions.kt.mustache new file mode 100644 index 0000000000..0a42ce534d --- /dev/null +++ b/modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/ApiAbstractions.kt.mustache @@ -0,0 +1,20 @@ +package {{packageName}}.infrastructure + +typealias MultiValueMap = Map> + +fun collectionDelimiter(collectionFormat: String) = when(collectionFormat) { + "csv" -> "," + "tsv" -> "\t" + "pipes" -> "|" + "ssv" -> " " + else -> "" +} + +val defaultMultiValueConverter: (item: Any?) -> String = { item -> "$item" } + +fun toMultiValue(items: List, collectionFormat: String, map: (item: Any?) -> String = defaultMultiValueConverter): List { + return when(collectionFormat) { + "multi" -> items.map(map) + else -> listOf(items.map(map).joinToString(separator = collectionDelimiter(collectionFormat))) + } +} \ No newline at end of file diff --git a/modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/ApiClient.kt.mustache b/modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/ApiClient.kt.mustache index 7480a80fce..49517bd159 100644 --- a/modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/ApiClient.kt.mustache +++ b/modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/ApiClient.kt.mustache @@ -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() diff --git a/modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/RequestConfig.kt.mustache b/modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/RequestConfig.kt.mustache index 913b42b648..58a3d605aa 100644 --- a/modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/RequestConfig.kt.mustache +++ b/modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/RequestConfig.kt.mustache @@ -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 because rfc2616 defines + * multi-valued headers as csv-only. */ data class RequestConfig( val method: RequestMethod, val path: String, val headers: Map = mapOf(), - val query: Map = mapOf()) \ No newline at end of file + val query: Map> = mapOf()) \ No newline at end of file diff --git a/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenModelTest.java b/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenModelTest.java index 474806638d..23bf434bf3 100644 --- a/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenModelTest.java +++ b/modules/swagger-codegen/src/test/java/io/swagger/codegen/kotlin/KotlinClientCodegenModelTest.java @@ -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"); + Assert.assertEquals(property.datatype, "kotlin.Array"); 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); diff --git a/samples/client/petstore/kotlin/docs/Pet.md b/samples/client/petstore/kotlin/docs/Pet.md index 73963abeb0..ec77560073 100644 --- a/samples/client/petstore/kotlin/docs/Pet.md +++ b/samples/client/petstore/kotlin/docs/Pet.md @@ -7,8 +7,8 @@ Name | Type | Description | Notes **id** | **kotlin.Long** | | [optional] **category** | [**Category**](Category.md) | | [optional] **name** | **kotlin.String** | | -**photoUrls** | **kotlin.collections.List<kotlin.String>** | | -**tags** | [**kotlin.collections.List<Tag>**](Tag.md) | | [optional] +**photoUrls** | **kotlin.Array<kotlin.String>** | | +**tags** | [**kotlin.Array<Tag>**](Tag.md) | | [optional] **status** | [**inline**](#StatusEnum) | pet status in the store | [optional] diff --git a/samples/client/petstore/kotlin/docs/PetApi.md b/samples/client/petstore/kotlin/docs/PetApi.md index 39803d4af4..29fb5bd0ee 100644 --- a/samples/client/petstore/kotlin/docs/PetApi.md +++ b/samples/client/petstore/kotlin/docs/PetApi.md @@ -110,7 +110,7 @@ null (empty response body) # **findPetsByStatus** -> kotlin.collections.List<Pet> findPetsByStatus(status) +> kotlin.Array<Pet> findPetsByStatus(status) Finds Pets by status @@ -123,9 +123,9 @@ Multiple status values can be provided with comma separated strings //import io.swagger.client.models.* val apiInstance = PetApi() -val status : kotlin.collections.List = // kotlin.collections.List | Status values that need to be considered for filter +val status : kotlin.Array = // kotlin.Array | Status values that need to be considered for filter try { - val result : kotlin.collections.List = apiInstance.findPetsByStatus(status) + val result : kotlin.Array = apiInstance.findPetsByStatus(status) println(result) } catch (e: ClientException) { println("4xx response calling PetApi#findPetsByStatus") @@ -140,11 +140,11 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **status** | [**kotlin.collections.List<kotlin.String>**](kotlin.String.md)| Status values that need to be considered for filter | [enum: available, pending, sold] + **status** | [**kotlin.Array<kotlin.String>**](kotlin.String.md)| Status values that need to be considered for filter | [enum: available, pending, sold] ### Return type -[**kotlin.collections.List<Pet>**](Pet.md) +[**kotlin.Array<Pet>**](Pet.md) ### Authorization @@ -157,7 +157,7 @@ Name | Type | Description | Notes # **findPetsByTags** -> kotlin.collections.List<Pet> findPetsByTags(tags) +> kotlin.Array<Pet> findPetsByTags(tags) Finds Pets by tags @@ -170,9 +170,9 @@ Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 //import io.swagger.client.models.* val apiInstance = PetApi() -val tags : kotlin.collections.List = // kotlin.collections.List | Tags to filter by +val tags : kotlin.Array = // kotlin.Array | Tags to filter by try { - val result : kotlin.collections.List = apiInstance.findPetsByTags(tags) + val result : kotlin.Array = apiInstance.findPetsByTags(tags) println(result) } catch (e: ClientException) { println("4xx response calling PetApi#findPetsByTags") @@ -187,11 +187,11 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **tags** | [**kotlin.collections.List<kotlin.String>**](kotlin.String.md)| Tags to filter by | + **tags** | [**kotlin.Array<kotlin.String>**](kotlin.String.md)| Tags to filter by | ### Return type -[**kotlin.collections.List<Pet>**](Pet.md) +[**kotlin.Array<Pet>**](Pet.md) ### Authorization diff --git a/samples/client/petstore/kotlin/docs/UserApi.md b/samples/client/petstore/kotlin/docs/UserApi.md index 0974c7f83c..d15aae23da 100644 --- a/samples/client/petstore/kotlin/docs/UserApi.md +++ b/samples/client/petstore/kotlin/docs/UserApi.md @@ -75,7 +75,7 @@ Creates list of users with given input array //import io.swagger.client.models.* val apiInstance = UserApi() -val body : kotlin.collections.List = // kotlin.collections.List | List of user object +val body : kotlin.Array = // kotlin.Array | List of user object try { apiInstance.createUsersWithArrayInput(body) } catch (e: ClientException) { @@ -91,7 +91,7 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **body** | [**kotlin.collections.List<User>**](User.md)| List of user object | + **body** | [**kotlin.Array<User>**](User.md)| List of user object | ### Return type @@ -121,7 +121,7 @@ Creates list of users with given input array //import io.swagger.client.models.* val apiInstance = UserApi() -val body : kotlin.collections.List = // kotlin.collections.List | List of user object +val body : kotlin.Array = // kotlin.Array | List of user object try { apiInstance.createUsersWithListInput(body) } catch (e: ClientException) { @@ -137,7 +137,7 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- - **body** | [**kotlin.collections.List<User>**](User.md)| List of user object | + **body** | [**kotlin.Array<User>**](User.md)| List of user object | ### Return type diff --git a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/apis/PetApi.kt b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/apis/PetApi.kt index a01400aa9d..c716883b34 100644 --- a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/apis/PetApi.kt +++ b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/apis/PetApi.kt @@ -26,7 +26,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ fun addPet(body: Pet) : Unit { val localVariableBody: kotlin.Any? = body - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.POST, @@ -58,7 +58,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ fun deletePet(petId: kotlin.Long, apiKey: kotlin.String) : Unit { val localVariableBody: kotlin.Any? = null - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf("apiKey" to apiKey) val localVariableConfig = RequestConfig( RequestMethod.DELETE, @@ -85,12 +85,12 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli * Finds Pets by status * Multiple status values can be provided with comma separated strings * @param status Status values that need to be considered for filter - * @return kotlin.collections.List + * @return kotlin.Array */ @Suppress("UNCHECKED_CAST") - fun findPetsByStatus(status: kotlin.collections.List) : kotlin.collections.List { + fun findPetsByStatus(status: kotlin.Array) : kotlin.Array { val localVariableBody: kotlin.Any? = null - val localVariableQuery: kotlin.collections.Map = mapOf("status" to status.joinToString(separator = collectionDelimiter("csv"))) + val localVariableQuery: MultiValueMap = mapOf("status" to toMultiValue(status.toList(), "csv")) val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.GET, @@ -98,13 +98,13 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli query = localVariableQuery, headers = localVariableHeaders ) - val response = request>( + val response = request>( localVariableConfig, localVariableBody ) return when (response.responseType) { - ResponseType.Success -> (response as Success<*>).data as kotlin.collections.List + ResponseType.Success -> (response as Success<*>).data as kotlin.Array ResponseType.Informational -> TODO() ResponseType.Redirection -> TODO() ResponseType.ClientError -> throw ClientException((response as ClientError<*>).body as? String ?: "Client error") @@ -117,12 +117,12 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli * Finds Pets by tags * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing. * @param tags Tags to filter by - * @return kotlin.collections.List + * @return kotlin.Array */ @Suppress("UNCHECKED_CAST") - fun findPetsByTags(tags: kotlin.collections.List) : kotlin.collections.List { + fun findPetsByTags(tags: kotlin.Array) : kotlin.Array { val localVariableBody: kotlin.Any? = null - val localVariableQuery: kotlin.collections.Map = mapOf("tags" to tags.joinToString(separator = collectionDelimiter("csv"))) + val localVariableQuery: MultiValueMap = mapOf("tags" to toMultiValue(tags.toList(), "csv")) val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.GET, @@ -130,13 +130,13 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli query = localVariableQuery, headers = localVariableHeaders ) - val response = request>( + val response = request>( localVariableConfig, localVariableBody ) return when (response.responseType) { - ResponseType.Success -> (response as Success<*>).data as kotlin.collections.List + ResponseType.Success -> (response as Success<*>).data as kotlin.Array ResponseType.Informational -> TODO() ResponseType.Redirection -> TODO() ResponseType.ClientError -> throw ClientException((response as ClientError<*>).body as? String ?: "Client error") @@ -154,7 +154,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli @Suppress("UNCHECKED_CAST") fun getPetById(petId: kotlin.Long) : Pet { val localVariableBody: kotlin.Any? = null - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.GET, @@ -185,7 +185,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ fun updatePet(body: Pet) : Unit { val localVariableBody: kotlin.Any? = body - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.PUT, @@ -218,7 +218,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli */ fun updatePetWithForm(petId: kotlin.Long, name: kotlin.String, status: kotlin.String) : Unit { val localVariableBody: kotlin.Any? = mapOf("name" to "$name", "status" to "$status") - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf("Content-Type" to "multipart/form-data") val localVariableConfig = RequestConfig( RequestMethod.POST, @@ -252,7 +252,7 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli @Suppress("UNCHECKED_CAST") fun uploadFile(petId: kotlin.Long, additionalMetadata: kotlin.String, file: java.io.File) : ApiResponse { val localVariableBody: kotlin.Any? = mapOf("additionalMetadata" to "$additionalMetadata", "file" to "$file") - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf("Content-Type" to "multipart/form-data") val localVariableConfig = RequestConfig( RequestMethod.POST, @@ -275,12 +275,4 @@ class PetApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCli } } - - private fun collectionDelimiter(collectionFormat: kotlin.String) = when(collectionFormat) { - "csv" -> "," - "tsv" -> "\t" - "pipes" -> "|" - "ssv" -> " " - else -> "" - } } diff --git a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/apis/StoreApi.kt b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/apis/StoreApi.kt index 9dabfdb3b6..53e3813f4d 100644 --- a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/apis/StoreApi.kt +++ b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/apis/StoreApi.kt @@ -25,7 +25,7 @@ class StoreApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiC */ fun deleteOrder(orderId: kotlin.String) : Unit { val localVariableBody: kotlin.Any? = null - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.DELETE, @@ -56,7 +56,7 @@ class StoreApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiC @Suppress("UNCHECKED_CAST") fun getInventory() : kotlin.collections.Map { val localVariableBody: kotlin.Any? = null - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.GET, @@ -88,7 +88,7 @@ class StoreApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiC @Suppress("UNCHECKED_CAST") fun getOrderById(orderId: kotlin.Long) : Order { val localVariableBody: kotlin.Any? = null - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.GET, @@ -120,7 +120,7 @@ class StoreApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiC @Suppress("UNCHECKED_CAST") fun placeOrder(body: Order) : Order { val localVariableBody: kotlin.Any? = body - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.POST, @@ -143,12 +143,4 @@ class StoreApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiC } } - - private fun collectionDelimiter(collectionFormat: kotlin.String) = when(collectionFormat) { - "csv" -> "," - "tsv" -> "\t" - "pipes" -> "|" - "ssv" -> " " - else -> "" - } } diff --git a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/apis/UserApi.kt b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/apis/UserApi.kt index e703c71bcd..d3d80fe76a 100644 --- a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/apis/UserApi.kt +++ b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/apis/UserApi.kt @@ -25,7 +25,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl */ fun createUser(body: User) : Unit { val localVariableBody: kotlin.Any? = body - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.POST, @@ -54,9 +54,9 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl * @param body List of user object * @return void */ - fun createUsersWithArrayInput(body: kotlin.collections.List) : Unit { + fun createUsersWithArrayInput(body: kotlin.Array) : Unit { val localVariableBody: kotlin.Any? = body - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.POST, @@ -85,9 +85,9 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl * @param body List of user object * @return void */ - fun createUsersWithListInput(body: kotlin.collections.List) : Unit { + fun createUsersWithListInput(body: kotlin.Array) : Unit { val localVariableBody: kotlin.Any? = body - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.POST, @@ -118,7 +118,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl */ fun deleteUser(username: kotlin.String) : Unit { val localVariableBody: kotlin.Any? = null - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.DELETE, @@ -150,7 +150,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl @Suppress("UNCHECKED_CAST") fun getUserByName(username: kotlin.String) : User { val localVariableBody: kotlin.Any? = null - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.GET, @@ -183,7 +183,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl @Suppress("UNCHECKED_CAST") fun loginUser(username: kotlin.String, password: kotlin.String) : kotlin.String { val localVariableBody: kotlin.Any? = null - val localVariableQuery: kotlin.collections.Map = mapOf("username" to username, "password" to password) + val localVariableQuery: MultiValueMap = mapOf("username" to listOf("$username"), "password" to listOf("$password")) val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.GET, @@ -213,7 +213,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl */ fun logoutUser() : Unit { val localVariableBody: kotlin.Any? = null - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.GET, @@ -245,7 +245,7 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl */ fun updateUser(username: kotlin.String, body: User) : Unit { val localVariableBody: kotlin.Any? = body - val localVariableQuery: kotlin.collections.Map = mapOf() + val localVariableQuery: MultiValueMap = mapOf() val localVariableHeaders: kotlin.collections.Map = mapOf() val localVariableConfig = RequestConfig( RequestMethod.PUT, @@ -268,12 +268,4 @@ class UserApi(basePath: kotlin.String = "http://petstore.swagger.io/v2") : ApiCl } } - - private fun collectionDelimiter(collectionFormat: kotlin.String) = when(collectionFormat) { - "csv" -> "," - "tsv" -> "\t" - "pipes" -> "|" - "ssv" -> " " - else -> "" - } } diff --git a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/infrastructure/ApiAbstractions.kt b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/infrastructure/ApiAbstractions.kt new file mode 100644 index 0000000000..87cc13d045 --- /dev/null +++ b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/infrastructure/ApiAbstractions.kt @@ -0,0 +1,20 @@ +package io.swagger.client.infrastructure + +typealias MultiValueMap = Map> + +fun collectionDelimiter(collectionFormat: String) = when(collectionFormat) { + "csv" -> "," + "tsv" -> "\t" + "pipes" -> "|" + "ssv" -> " " + else -> "" +} + +val defaultMultiValueConverter: (item: Any?) -> String = { item -> "$item" } + +fun toMultiValue(items: List, collectionFormat: String, map: (item: Any?) -> String = defaultMultiValueConverter): List { + return when(collectionFormat) { + "multi" -> items.map(map) + else -> listOf(items.map(map).joinToString(separator = collectionDelimiter(collectionFormat))) + } +} \ No newline at end of file diff --git a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/infrastructure/ApiClient.kt b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/infrastructure/ApiClient.kt index 05ff12c0f9..40d771e475 100644 --- a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/infrastructure/ApiClient.kt +++ b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/infrastructure/ApiClient.kt @@ -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() diff --git a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/infrastructure/RequestConfig.kt b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/infrastructure/RequestConfig.kt index 1df8b769db..3825588ce4 100644 --- a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/infrastructure/RequestConfig.kt +++ b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/infrastructure/RequestConfig.kt @@ -5,9 +5,11 @@ package io.swagger.client.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 because rfc2616 defines + * multi-valued headers as csv-only. */ data class RequestConfig( val method: RequestMethod, val path: String, val headers: Map = mapOf(), - val query: Map = mapOf()) \ No newline at end of file + val query: Map> = mapOf()) \ No newline at end of file diff --git a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/models/Pet.kt b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/models/Pet.kt index 5fe800afe7..68ee9be1b4 100644 --- a/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/models/Pet.kt +++ b/samples/client/petstore/kotlin/src/main/kotlin/io/swagger/client/models/Pet.kt @@ -25,10 +25,10 @@ import io.swagger.client.models.Tag */ data class Pet ( val name: kotlin.String, - val photoUrls: kotlin.collections.List, + val photoUrls: kotlin.Array, val id: kotlin.Long? = null, val category: Category? = null, - val tags: kotlin.collections.List? = null, + val tags: kotlin.Array? = null, /* pet status in the store */ val status: Pet.Status? = null ) { diff --git a/samples/client/petstore/kotlin/src/test/kotlin/io/swagger/client/functional/EvaluateTest.kt b/samples/client/petstore/kotlin/src/test/kotlin/io/swagger/client/functional/EvaluateTest.kt index 955560fb79..db40b04f2e 100644 --- a/samples/client/petstore/kotlin/src/test/kotlin/io/swagger/client/functional/EvaluateTest.kt +++ b/samples/client/petstore/kotlin/src/test/kotlin/io/swagger/client/functional/EvaluateTest.kt @@ -2,16 +2,21 @@ package io.swagger.client.functional import io.kotlintest.matchers.should import io.kotlintest.matchers.beGreaterThan +import io.kotlintest.matchers.shouldEqual import io.kotlintest.specs.ShouldSpec import io.swagger.client.apis.PetApi +import io.swagger.client.models.Pet class EvaluateTest : ShouldSpec() { init { should("query against pet statuses") { val api = PetApi() - val results = api.findPetsByStatus(listOf("sold")) + val results = api.findPetsByStatus(arrayOf("sold")) results.size should beGreaterThan(1) + + // Pet is lazily deserialized here. Need to iterate to verify all "sold" statuses. + results.all { it.status == Pet.Status.sold } shouldEqual true } // TODO: Handle default (200) response