Repository: cjbooms/fabrikt
Pull request #49 (2021-06-03 11:57:41)

Support explode style query parameters which are the default
Support explode style query parameters which are the default


Patch:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpSimpleClientGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpSimpleClientGenerator.kt
index be8c437..eb962fa 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpSimpleClientGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpSimpleClientGenerator.kt
@@ -141,18 +141,18 @@ data class SimpleClientOperationStatement(
     }
 
     /**
-     * By now it's only supported `form` style query params with a comma-separated delimiter. See [Open API 3.0
+     * Only supports `form` style query params with either explode true or false. See [Open API 3.0
      * serialization](https://swagger.io/docs/specification/serialization) query parameters style values
      */
     private fun CodeBlock.Builder.addQueryParamStatement(): CodeBlock.Builder {
         operation.getQueryParams().map {
             when (KotlinTypeInfo.from(it.schema)) {
                 is KotlinTypeInfo.Array -> this.add(
-                    "\n.%T(%S, %N, %S)",
+                    "\n.%T(%S, %N, %L)",
                     "queryParam".toClassName(packages.client),
                     it.name,
                     it.name.toKCodeName(),
-                    ","
+                    if (it.explode == null || it.explode == true) "true" else "false"
                 )
                 else -> this.add(
                     "\n.%T(%S, %N)",
diff --git a/src/main/resources/templates/client-code/http-util.kt.hbs b/src/main/resources/templates/client-code/http-util.kt.hbs
index 043329c..f0d3507 100644
--- a/src/main/resources/templates/client-code/http-util.kt.hbs
+++ b/src/main/resources/templates/client-code/http-util.kt.hbs
@@ -11,27 +11,26 @@ import okhttp3.Response
 import okhttp3.ResponseBody
 
 @Suppress("unused")
-fun <T : Any> HttpUrl.Builder.queryParam(key: String, value: T?): HttpUrl.Builder {
-    value?.let { this.addQueryParameter(key, it.toString()) }
-    return this
+fun <T : Any> HttpUrl.Builder.queryParam(key: String, value: T?): HttpUrl.Builder = this.apply {
+    if (value != null) this.addQueryParameter(key, value.toString())
 }
 
 @Suppress("unused")
-fun <T : Any> FormBody.Builder.formParam(key: String, value: T?): FormBody.Builder {
-    value?.let { this.add(key, it.toString()) }
-    return this
+fun <T : Any> FormBody.Builder.formParam(key: String, value: T?): FormBody.Builder = this.apply {
+    if (value != null) this.add(key, value.toString())
 }
 
 @Suppress("unused")
-fun HttpUrl.Builder.queryParam(key: String, values: List<String>?, delimiter: String): HttpUrl.Builder {
-    values?.let { this.addQueryParameter(key, it.joinToString(delimiter)) }
-    return this
+fun HttpUrl.Builder.queryParam(key: String, values: List<String>?, explode: Boolean = true) = this.apply {
+    if (values != null) {
+        if (explode) values.forEach { addQueryParameter(key, it) }
+        else addQueryParameter(key, values.joinToString())
+    }
 }
 
 @Suppress("unused")
-fun Headers.Builder.header(key: String, value: String?): Headers.Builder {
-    value?.let { this.add(key, it) }
-    return this
+fun Headers.Builder.header(key: String, value: String?): Headers.Builder = this.apply {
+    if (value != null) this.add(key, value)
 }
 
 @Throws(ApiException::class)


Test patch:
diff --git a/src/test/resources/examples/okHttpClient/api.yaml b/src/test/resources/examples/okHttpClient/api.yaml
index 5e2f379..251864e 100644
--- a/src/test/resources/examples/okHttpClient/api.yaml
+++ b/src/test/resources/examples/okHttpClient/api.yaml
@@ -7,7 +7,7 @@ paths:
     get:
       summary: "GET example path 1"
       parameters:
-        - $ref: "#/components/parameters/QueryParam1"
+        - $ref: "#/components/parameters/ListQueryParamExploded"
         - $ref: "#/components/parameters/QueryParam2"
       responses:
         200:
@@ -23,7 +23,7 @@ paths:
     post:
       summary: "POST example path 1"
       parameters:
-        - $ref: "#/components/parameters/QueryParam1"
+        - $ref: "#/components/parameters/ListQueryParamExploded"
       requestBody:
         $ref: "#/components/requestBodies/PostBody"
       responses:
@@ -88,7 +88,7 @@ paths:
     put:
       summary: "PUT example path 3"
       parameters:
-        - $ref: "#/components/parameters/QueryParam1"
+        - $ref: "#/components/parameters/ListQueryParamCsv"
         - $ref: "#/components/parameters/PathParam"
         - $ref: "#/components/parameters/IfMatch"
       requestBody:
@@ -123,8 +123,8 @@ components:
         type: "string"
       required: false
 
-    QueryParam1:
-      name: "query_param1"
+    ListQueryParamExploded:
+      name: "explode_list_query_param"
       in: "query"
       schema:
         type: "array"
@@ -132,6 +132,17 @@ components:
           type: "string"
       required: false
       allowEmptyValue: false
+      
+    ListQueryParamCsv:
+      name: "csv_list_query_param"
+      in: "query"
+      explode: false
+      schema:
+        type: "array"
+        items:
+          type: "string"
+      required: false
+      allowEmptyValue: false
 
     QueryParam2:
       name: "query_param2"
diff --git a/src/test/resources/examples/okHttpClient/client/ApiClient.kt b/src/test/resources/examples/okHttpClient/client/ApiClient.kt
index 6ffd262..2426e41 100644
--- a/src/test/resources/examples/okHttpClient/client/ApiClient.kt
+++ b/src/test/resources/examples/okHttpClient/client/ApiClient.kt
@@ -29,15 +29,16 @@ class ExamplePathClient(
     /**
      * GET example path 1
      *
-     * @param queryParam1
+     * @param explodeListQueryParam
      * @param queryParam2
      */
     @Throws(ApiException::class)
-    fun getExamplePath1(queryParam1: List<String>?, queryParam2: Int?): ApiResponse<QueryResult?> {
+    fun getExamplePath1(explodeListQueryParam: List<String>?, queryParam2: Int?):
+        ApiResponse<QueryResult?> {
         val httpUrl: HttpUrl = "$baseUrl/example-path-1"
             .toHttpUrl()
             .newBuilder()
-            .queryParam("query_param1", queryParam1, ",")
+            .queryParam("explode_list_query_param", explodeListQueryParam, true)
             .queryParam("query_param2", queryParam2)
             .build()
 
@@ -57,14 +58,15 @@ class ExamplePathClient(
     /**
      * POST example path 1
      *
-     * @param queryParam1
+     * @param explodeListQueryParam
      */
     @Throws(ApiException::class)
-    fun postExamplePath1(generatedType: Content, queryParam1: List<String>?): ApiResponse<Unit?> {
+    fun postExamplePath1(generatedType: Content, explodeListQueryParam: List<String>?):
+        ApiResponse<Unit?> {
         val httpUrl: HttpUrl = "$baseUrl/example-path-1"
             .toHttpUrl()
             .newBuilder()
-            .queryParam("query_param1", queryParam1, ",")
+            .queryParam("explode_list_query_param", explodeListQueryParam, true)
             .build()
 
         val httpHeaders: Headers = Headers.Builder()
@@ -187,14 +189,14 @@ class ExamplePathSubresourceClient(
     /**
      * PUT example path 3
      *
-     * @param queryParam1
+     * @param csvListQueryParam
      * @param pathParam The resource id
      * @param ifMatch The RFC7232 If-Match header field
      */
     @Throws(ApiException::class)
     fun putExamplePath3PathParamSubresource(
         firstModel: FirstModel,
-        queryParam1: List<String>?,
+        csvListQueryParam: List<String>?,
         pathParam: String,
         ifMatch: String
     ): ApiResponse<Unit?> {
@@ -202,7 +204,7 @@ class ExamplePathSubresourceClient(
             .pathParam("{path_param}" to pathParam)
             .toHttpUrl()
             .newBuilder()
-            .queryParam("query_param1", queryParam1, ",")
+            .queryParam("csv_list_query_param", csvListQueryParam, false)
             .build()
 
         val httpHeaders: Headers = Headers.Builder()
diff --git a/src/test/resources/examples/okHttpClient/client/ApiService.kt b/src/test/resources/examples/okHttpClient/client/ApiService.kt
index ebc668b..d62e28a 100644
--- a/src/test/resources/examples/okHttpClient/client/ApiService.kt
+++ b/src/test/resources/examples/okHttpClient/client/ApiService.kt
@@ -34,15 +34,17 @@ class ExamplePathService(
     private val apiClient: ExamplePathClient = ExamplePathClient(objectMapper, baseUrl, client)
 
     @Throws(ApiException::class)
-    fun getExamplePath1(queryParam1: List<String>?, queryParam2: Int?): ApiResponse<QueryResult?> =
+    fun getExamplePath1(explodeListQueryParam: List<String>?, queryParam2: Int?):
+        ApiResponse<QueryResult?> =
         withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
-            apiClient.getExamplePath1(queryParam1, queryParam2)
+            apiClient.getExamplePath1(explodeListQueryParam, queryParam2)
         }
 
     @Throws(ApiException::class)
-    fun postExamplePath1(generatedType: Content, queryParam1: List<String>?): ApiResponse<Unit?> =
+    fun postExamplePath1(generatedType: Content, explodeListQueryParam: List<String>?):
+        ApiResponse<Unit?> =
         withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
-            apiClient.postExamplePath1(generatedType, queryParam1)
+            apiClient.postExamplePath1(generatedType, explodeListQueryParam)
         }
 
     @Throws(ApiException::class)
@@ -102,11 +104,11 @@ class ExamplePathSubresourceService(
     @Throws(ApiException::class)
     fun putExamplePath3PathParamSubresource(
         firstModel: FirstModel,
-        queryParam1: List<String>?,
+        csvListQueryParam: List<String>?,
         pathParam: String,
         ifMatch: String
     ): ApiResponse<Unit?> =
         withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
-            apiClient.putExamplePath3PathParamSubresource(firstModel, queryParam1, pathParam, ifMatch)
+            apiClient.putExamplePath3PathParamSubresource(firstModel, csvListQueryParam, pathParam, ifMatch)
         }
 }
diff --git a/src/test/resources/examples/okHttpClient/client/HttpUtil.kt b/src/test/resources/examples/okHttpClient/client/HttpUtil.kt
index 13b6151..3a398ac 100644
--- a/src/test/resources/examples/okHttpClient/client/HttpUtil.kt
+++ b/src/test/resources/examples/okHttpClient/client/HttpUtil.kt
@@ -11,27 +11,26 @@ import okhttp3.Response
 import okhttp3.ResponseBody
 
 @Suppress("unused")
-fun <T : Any> HttpUrl.Builder.queryParam(key: String, value: T?): HttpUrl.Builder {
-    value?.let { this.addQueryParameter(key, it.toString()) }
-    return this
+fun <T : Any> HttpUrl.Builder.queryParam(key: String, value: T?): HttpUrl.Builder = this.apply {
+    if (value != null) this.addQueryParameter(key, value.toString())
 }
 
 @Suppress("unused")
-fun <T : Any> FormBody.Builder.formParam(key: String, value: T?): FormBody.Builder {
-    value?.let { this.add(key, it.toString()) }
-    return this
+fun <T : Any> FormBody.Builder.formParam(key: String, value: T?): FormBody.Builder = this.apply {
+    if (value != null) this.add(key, value.toString())
 }
 
 @Suppress("unused")
-fun HttpUrl.Builder.queryParam(key: String, values: List<String>?, delimiter: String): HttpUrl.Builder {
-    values?.let { this.addQueryParameter(key, it.joinToString(delimiter)) }
-    return this
+fun HttpUrl.Builder.queryParam(key: String, values: List<String>?, explode: Boolean = true) = this.apply {
+    if (values != null) {
+        if (explode) values.forEach { addQueryParameter(key, it) }
+        else addQueryParameter(key, values.joinToString())
+    }
 }
 
 @Suppress("unused")
-fun Headers.Builder.header(key: String, value: String?): Headers.Builder {
-    value?.let { this.add(key, it) }
-    return this
+fun Headers.Builder.header(key: String, value: String?): Headers.Builder = this.apply {
+    if (value != null) this.add(key, value)
 }
 
 @Throws(ApiException::class)