Skip to content

Commit 9535ea2

Browse files
Astakhov, AndrewAstakhov, Andrew
authored andcommitted
[Kotlin] Handle File Downloading
1 parent 2864125 commit 9535ea2

File tree

17 files changed

+327
-67
lines changed

17 files changed

+327
-67
lines changed

modules/swagger-codegen/src/main/resources/kotlin-client/api.mustache

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,13 @@ class {{classname}}(basePath: kotlin.String = "{{{basePath}}}") : ApiClient(base
2323
fun {{operationId}}({{#allParams}}{{paramName}}: {{{dataType}}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) : {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Unit{{/returnType}} {
2424
val localVariableBody: kotlin.Any? = {{#hasBodyParam}}{{#bodyParams}}{{paramName}}{{/bodyParams}}{{/hasBodyParam}}{{^hasBodyParam}}{{^hasFormParams}}null{{/hasFormParams}}{{#hasFormParams}}mapOf({{#formParams}}"{{{baseName}}}" to "${{paramName}}"{{#hasMore}}, {{/hasMore}}{{/formParams}}){{/hasFormParams}}{{/hasBodyParam}}
2525
val localVariableQuery: MultiValueMap = {{^hasQueryParams}}mapOf(){{/hasQueryParams}}{{#hasQueryParams}}mapOf({{#queryParams}}"{{baseName}}" to {{#isContainer}}toMultiValue({{paramName}}.toList(), "{{collectionFormat}}"){{/isContainer}}{{^isContainer}}listOf("${{paramName}}"){{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/queryParams}}){{/hasQueryParams}}
26-
val localVariableHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf({{#hasFormParams}}"Content-Type" to "multipart/form-data"{{/hasFormParams}}{{^hasHeaderParams}}){{/hasHeaderParams}}{{#hasHeaderParams}}{{#hasFormParams}}, {{/hasFormParams}}{{#headerParams}}"{{baseName}}" to {{#isContainer}}{{paramName}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}"){{/isContainer}}{{^isContainer}}{{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/headerParams}}){{/hasHeaderParams}}
26+
27+
val contentHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf({{#hasFormParams}}"Content-Type" to "multipart/form-data"{{/hasFormParams}})
28+
val acceptsHeaders: kotlin.collections.Map<kotlin.String,kotlin.String> = mapOf({{#hasProduces}}"Accept" to "{{#produces}}{{#isContainer}}{{mediaType}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}"){{/isContainer}}{{^isContainer}}{{mediaType}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/produces}}"{{/hasProduces}})
29+
val localVariableHeaders: kotlin.collections.MutableMap<kotlin.String,kotlin.String> = mutableMapOf({{#hasHeaderParams}}{{#headerParams}}"{{baseName}}" to {{#isContainer}}{{paramName}}.joinToString(separator = collectionDelimiter("{{collectionFormat}}"){{/isContainer}}{{^isContainer}}{{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/headerParams}}{{/hasHeaderParams}})
30+
localVariableHeaders.putAll(contentHeaders)
31+
localVariableHeaders.putAll(acceptsHeaders)
32+
2733
val localVariableConfig = RequestConfig(
2834
RequestMethod.{{httpMethod}},
2935
"{{path}}"{{#pathParams}}.replace("{"+"{{baseName}}"+"}", "${{paramName}}"){{/pathParams}},

modules/swagger-codegen/src/main/resources/kotlin-client/infrastructure/ApiClient.kt.mustache

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package {{packageName}}.infrastructure
22

33
import okhttp3.*
44
import java.io.File
5+
import java.io.IOException
6+
import java.util.regex.Pattern
57

68
open class ApiClient(val baseUrl: String) {
79
companion object {
@@ -51,12 +53,31 @@ open class ApiClient(val baseUrl: String) {
5153
TODO("requestBody currently only supports JSON body and File body.")
5254
}
5355
54-
inline protected fun <reified T: Any?> responseBody(body: ResponseBody?, mediaType: String = JsonMediaType): T? {
55-
if(body == null) return null
56-
return when(mediaType) {
57-
JsonMediaType -> Serializer.moshi.adapter(T::class.java).fromJson(body.source())
58-
else -> TODO()
56+
inline protected fun <reified T: Any?> responseBody(response: Response, mediaType: String = JsonMediaType): T? {
57+
if(response.body() == null) return null
58+
59+
if(T::class.java == java.io.File::class.java){
60+
return downloadFileFromResponse(response) as T
5961
}
62+
63+
var contentType = response.headers().get("Content-Type")
64+
65+
if(contentType == null) {
66+
contentType = JsonMediaType
67+
}
68+
69+
if(isJsonMime(contentType)){
70+
return Serializer.moshi.adapter(T::class.java).fromJson(response.body()?.source())
71+
} else if(contentType.equals(String.javaClass)){
72+
return response.body().toString() as T
73+
} else {
74+
TODO("Fill in more types!")
75+
}
76+
}
77+
78+
fun isJsonMime(mime: String?): Boolean {
79+
val jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$"
80+
return mime != null && (mime.matches(jsonMime.toRegex()) || mime == "*/*")
6081
}
6182
6283
inline protected fun <reified T: Any?> request(requestConfig: RequestConfig, body : Any? = null): ApiInfrastructureResponse<T?> {
@@ -72,7 +93,7 @@ open class ApiClient(val baseUrl: String) {
7293
}
7394
7495
val url = urlBuilder.build()
75-
val headers = requestConfig.headers + defaultHeaders
96+
val headers = defaultHeaders + requestConfig.headers
7697
7798
if(headers[ContentType] ?: "" == "") {
7899
throw kotlin.IllegalStateException("Missing Content-Type header. This is required.")
@@ -113,7 +134,7 @@ open class ApiClient(val baseUrl: String) {
113134
response.headers().toMultimap()
114135
)
115136
response.isSuccessful -> return Success(
116-
responseBody(response.body(), accept),
137+
responseBody(response, accept),
117138
response.code(),
118139
response.headers().toMultimap()
119140
)
@@ -130,4 +151,50 @@ open class ApiClient(val baseUrl: String) {
130151
)
131152
}
132153
}
154+
155+
@Throws(IOException::class)
156+
fun downloadFileFromResponse(response: Response): File {
157+
val file = prepareDownloadFile(response)
158+
159+
response.body()?.byteStream().use{ input ->
160+
File(file.path).outputStream().use { input?.copyTo(it) }
161+
}
162+
163+
return file
164+
}
165+
166+
@Throws(IOException::class)
167+
fun prepareDownloadFile(response: Response): File {
168+
var filename: String? = null
169+
var contentDisposition = response.headers().get("Content-Disposition")
170+
171+
if(contentDisposition != null && contentDisposition != ""){
172+
val pattern = Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?")
173+
val matcher = pattern.matcher(contentDisposition)
174+
175+
if (matcher.find())
176+
filename = matcher.group(1)
177+
}
178+
var prefix: String
179+
var suffix: String? = null
180+
181+
if (filename == null) {
182+
prefix = "download-"
183+
suffix = ""
184+
} else {
185+
val pos = filename.lastIndexOf('.')
186+
187+
if (pos == -1) {
188+
prefix = filename + "-";
189+
} else {
190+
prefix = filename.substring(0, pos) + "-"
191+
suffix = filename.substring(pos)
192+
}
193+
// File.createTempFile requires the prefix to be at least three characters long
194+
if (prefix.length < 3)
195+
prefix = "download-"
196+
}
197+
198+
return File.createTempFile(prefix, suffix);
199+
}
133200
}

samples/client/petstore/kotlin/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ This runs all tests and packages the library.
3131
<a name="documentation-for-api-endpoints"></a>
3232
## Documentation for API Endpoints
3333

34-
All URIs are relative to *http://petstore.swagger.io/v2*
34+
All URIs are relative to *https://petstore.swagger.io/v2*
3535

3636
Class | Method | HTTP request | Description
3737
------------ | ------------- | ------------- | -------------
@@ -83,7 +83,7 @@ Class | Method | HTTP request | Description
8383

8484
- **Type**: OAuth
8585
- **Flow**: implicit
86-
- **Authorization URL**: http://petstore.swagger.io/api/oauth/dialog
86+
- **Authorization URL**: https://petstore.swagger.io/oauth/dialog
8787
- **Scopes**:
8888
- write:pets: modify pets in your account
8989
- read:pets: read your pets

samples/client/petstore/kotlin/docs/PetApi.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# PetApi
22

3-
All URIs are relative to *http://petstore.swagger.io/v2*
3+
All URIs are relative to *https://petstore.swagger.io/v2*
44

55
Method | HTTP request | Description
66
------------- | ------------- | -------------
@@ -161,7 +161,7 @@ Name | Type | Description | Notes
161161
162162
Finds Pets by tags
163163

164-
Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
164+
Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
165165

166166
### Example
167167
```kotlin

samples/client/petstore/kotlin/docs/StoreApi.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# StoreApi
22

3-
All URIs are relative to *http://petstore.swagger.io/v2*
3+
All URIs are relative to *https://petstore.swagger.io/v2*
44

55
Method | HTTP request | Description
66
------------- | ------------- | -------------
@@ -16,7 +16,7 @@ Method | HTTP request | Description
1616
1717
Delete purchase order by ID
1818

19-
For valid response try integer IDs with value &lt; 1000. Anything above 1000 or nonintegers will generate API errors
19+
For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors
2020

2121
### Example
2222
```kotlin
@@ -25,7 +25,7 @@ For valid response try integer IDs with value &lt; 1000. Anything above 1000 or
2525
//import io.swagger.client.models.*
2626

2727
val apiInstance = StoreApi()
28-
val orderId : kotlin.String = orderId_example // kotlin.String | ID of the order that needs to be deleted
28+
val orderId : kotlin.Long = 789 // kotlin.Long | ID of the order that needs to be deleted
2929
try {
3030
apiInstance.deleteOrder(orderId)
3131
} catch (e: ClientException) {
@@ -41,7 +41,7 @@ try {
4141

4242
Name | Type | Description | Notes
4343
------------- | ------------- | ------------- | -------------
44-
**orderId** | **kotlin.String**| ID of the order that needs to be deleted |
44+
**orderId** | **kotlin.Long**| ID of the order that needs to be deleted |
4545

4646
### Return type
4747

@@ -105,7 +105,7 @@ This endpoint does not need any parameter.
105105
106106
Find purchase order by ID
107107

108-
For valid response try integer IDs with value &lt;&#x3D; 5 or &gt; 10. Other values will generated exceptions
108+
For valid response try integer IDs with value &gt;&#x3D; 1 and &lt;&#x3D; 10. Other values will generated exceptions
109109

110110
### Example
111111
```kotlin

samples/client/petstore/kotlin/docs/UserApi.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# UserApi
22

3-
All URIs are relative to *http://petstore.swagger.io/v2*
3+
All URIs are relative to *https://petstore.swagger.io/v2*
44

55
Method | HTTP request | Description
66
------------- | ------------- | -------------
@@ -213,7 +213,7 @@ Get user by user name
213213
//import io.swagger.client.models.*
214214

215215
val apiInstance = UserApi()
216-
val username : kotlin.String = username_example // kotlin.String | The name that needs to be fetched. Use user1 for testing.
216+
val username : kotlin.String = username_example // kotlin.String | The name that needs to be fetched. Use user1 for testing.
217217
try {
218218
val result : User = apiInstance.getUserByName(username)
219219
println(result)
@@ -230,7 +230,7 @@ try {
230230

231231
Name | Type | Description | Notes
232232
------------- | ------------- | ------------- | -------------
233-
**username** | **kotlin.String**| The name that needs to be fetched. Use user1 for testing. |
233+
**username** | **kotlin.String**| The name that needs to be fetched. Use user1 for testing. |
234234

235235
### Return type
236236

@@ -351,7 +351,7 @@ This can only be done by the logged in user.
351351
//import io.swagger.client.models.*
352352

353353
val apiInstance = UserApi()
354-
val username : kotlin.String = username_example // kotlin.String | name that need to be deleted
354+
val username : kotlin.String = username_example // kotlin.String | name that need to be updated
355355
val body : User = // User | Updated user object
356356
try {
357357
apiInstance.updateUser(username, body)
@@ -368,7 +368,7 @@ try {
368368

369369
Name | Type | Description | Notes
370370
------------- | ------------- | ------------- | -------------
371-
**username** | **kotlin.String**| name that need to be deleted |
371+
**username** | **kotlin.String**| name that need to be updated |
372372
**body** | [**User**](User.md)| Updated user object |
373373

374374
### Return type
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
rootProject.name = 'kotlin-petstore-client'
1+
rootProject.name = 'kotlin-client'

0 commit comments

Comments
 (0)