Repository: cjbooms/fabrikt
Pull request #83 (2021-10-11 12:45:13)

Adding the ability to set arbitrary additional HTTP headers. 
This will be convenient for things like propagating Open-Tracing telemetry headers


Patch:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGeneratorUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGeneratorUtils.kt
index cabb594..58e9780 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGeneratorUtils.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/ClientGeneratorUtils.kt
@@ -13,6 +13,7 @@ import com.squareup.kotlinpoet.asTypeName
 
 object ClientGeneratorUtils {
     const val ACCEPT_HEADER_VARIABLE_NAME = "acceptHeader"
+    const val ADDITIONAL_HEADERS_PARAMETER_NAME = "additionalHeaders"
 
     /**
      * It resolves the API operation response to its body type. It iterates over all non-default responses
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpEnhancedClientGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpEnhancedClientGenerator.kt
index 7f8d108..9422de7 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpEnhancedClientGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpEnhancedClientGenerator.kt
@@ -11,7 +11,9 @@ import com.cjbooms.fabrikt.generators.GeneratorUtils.toBodyParameterSpec
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toClassName
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toKCodeName
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toParameterSpec
+import com.cjbooms.fabrikt.generators.TypeFactory
 import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.ACCEPT_HEADER_VARIABLE_NAME
+import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.ADDITIONAL_HEADERS_PARAMETER_NAME
 import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.enhancedClientName
 import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.simpleClientName
 import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.toClientReturnType
@@ -63,13 +65,22 @@ class OkHttpEnhancedClientGenerator(
                                 .build(),
                             operation,
                         ) { it.hasMultipleContentMediaTypes() == true }
+                        .addParameter(
+                            ParameterSpec.builder(
+                                ADDITIONAL_HEADERS_PARAMETER_NAME,
+                                TypeFactory.createMapOfStringToType(String::class.asTypeName())
+                            )
+                                .defaultValue("emptyMap()")
+                                .build()
+                        )
                         .addCode(
                             Resilience4jClientOperationStatement(
                                 packages,
                                 resource,
                                 verb,
                                 operation
-                            ).toStatement())
+                            ).toStatement()
+                        )
                         .returns(operation.toClientReturnType(packages))
                         .build()
                 }
@@ -84,10 +95,11 @@ class OkHttpEnhancedClientGenerator(
         val circuitBreakerRegistryClassName =
             "CircuitBreakerRegistry".toClassName("io.github.resilience4j.circuitbreaker")
 
-        val configurableCircuitBreakerNameProperty = PropertySpec.builder("circuitBreakerName", String::class.asTypeName())
-            .mutable()
-            .initializer("%S", apiClientClassName.simpleName.toKCodeName())
-            .build()
+        val configurableCircuitBreakerNameProperty =
+            PropertySpec.builder("circuitBreakerName", String::class.asTypeName())
+                .mutable()
+                .initializer("%S", apiClientClassName.simpleName.toKCodeName())
+                .build()
 
         val clientProperty = PropertySpec.builder("apiClient", apiClientClassName, KModifier.PRIVATE)
             .initializer("%T(objectMapper, baseUrl, client)", apiClientClassName)
@@ -188,6 +200,14 @@ class Resilience4jClientOperationStatement(
                 params.add(listOf(ParameterSpec.builder(ACCEPT_HEADER_VARIABLE_NAME, String::class).build()))
             }
         }
+        params.add(
+            listOf(
+                ParameterSpec.builder(
+                    ADDITIONAL_HEADERS_PARAMETER_NAME,
+                    TypeFactory.createMapOfStringToType(String::class.asTypeName())
+                ).build()
+            )
+        )
 
         this.add("apiClient.%N(%L)", functionName(resource, verb), params.flatten().joinToString(",") { it.name })
         return this
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 d34bba1..1f1dcf7 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpSimpleClientGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpSimpleClientGenerator.kt
@@ -18,7 +18,9 @@ import com.cjbooms.fabrikt.generators.GeneratorUtils.toKCodeName
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toKdoc
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toParameterSpec
 import com.cjbooms.fabrikt.generators.GeneratorUtils.toVarName
+import com.cjbooms.fabrikt.generators.TypeFactory
 import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.ACCEPT_HEADER_VARIABLE_NAME
+import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.ADDITIONAL_HEADERS_PARAMETER_NAME
 import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.simpleClientName
 import com.cjbooms.fabrikt.generators.client.ClientGeneratorUtils.toClientReturnType
 import com.cjbooms.fabrikt.model.ClientType
@@ -64,6 +66,14 @@ class OkHttpSimpleClientGenerator(
                                 .build(),
                             operation,
                         ) { it.hasMultipleContentMediaTypes() == true }
+                        .addParameter(
+                            ParameterSpec.builder(
+                                ADDITIONAL_HEADERS_PARAMETER_NAME,
+                                TypeFactory.createMapOfStringToType(String::class.asTypeName())
+                            )
+                                .defaultValue("emptyMap()")
+                                .build()
+                        )
                         .addCode(
                             SimpleClientOperationStatement(
                                 packages,
@@ -176,7 +186,7 @@ data class SimpleClientOperationStatement(
     }
 
     private fun CodeBlock.Builder.addHeaderParamStatement(): CodeBlock.Builder {
-        this.add("\nval httpHeaders: %T = Headers.Builder()", "Headers".toClassName("okhttp3"))
+        this.add("\nval headerBuilder = Headers.Builder()")
         operation.getHeaderParams().map {
             this.add("\n.%T(%S, %N)", "header".toClassName(packages.client), it.name, it.name.toKCodeName())
         }
@@ -188,8 +198,9 @@ data class SimpleClientOperationStatement(
                 this.add("\n.%T(%S, %S)", "header".toClassName(packages.client), "Accept", operation.getPrimaryContentMediaTypeKey())
             }
         }
+        this.add("\nadditionalHeaders.forEach { headerBuilder.header(it.key, it.value) }")
 
-        return this.add("\n.build()\n")
+        return this.add("\nval httpHeaders: %T = headerBuilder.build()\n", "Headers".toClassName("okhttp3"))
     }
 
     private fun CodeBlock.Builder.addRequestStatement(): CodeBlock.Builder {


Test patch:
diff --git a/src/test/resources/examples/okHttpClient/client/ApiClient.kt b/src/test/resources/examples/okHttpClient/client/ApiClient.kt
index 047ccf9..9c2ff48 100644
--- a/src/test/resources/examples/okHttpClient/client/ApiClient.kt
+++ b/src/test/resources/examples/okHttpClient/client/ApiClient.kt
@@ -18,6 +18,7 @@ import kotlin.String
 import kotlin.Suppress
 import kotlin.Unit
 import kotlin.collections.List
+import kotlin.collections.Map
 import kotlin.jvm.Throws
 
 @Suppress("unused")
@@ -33,8 +34,11 @@ class ExamplePathClient(
      * @param queryParam2
      */
     @Throws(ApiException::class)
-    fun getExamplePath1(explodeListQueryParam: List<String>?, queryParam2: Int?):
-        ApiResponse<QueryResult?> {
+    fun getExamplePath1(
+        explodeListQueryParam: List<String>?,
+        queryParam2: Int?,
+        additionalHeaders: Map<String, String> = emptyMap()
+    ): ApiResponse<QueryResult?> {
         val httpUrl: HttpUrl = "$baseUrl/example-path-1"
             .toHttpUrl()
             .newBuilder()
@@ -42,9 +46,10 @@ class ExamplePathClient(
             .queryParam("query_param2", queryParam2)
             .build()
 
-        val httpHeaders: Headers = Headers.Builder()
+        val headerBuilder = Headers.Builder()
             .header("Accept", "application/vnd.custom.media+json")
-            .build()
+        additionalHeaders.forEach { headerBuilder.header(it.key, it.value) }
+        val httpHeaders: Headers = headerBuilder.build()
 
         val request: Request = Request.Builder()
             .url(httpUrl)
@@ -61,16 +66,20 @@ class ExamplePathClient(
      * @param explodeListQueryParam
      */
     @Throws(ApiException::class)
-    fun postExamplePath1(generatedType: Content, explodeListQueryParam: List<String>?):
-        ApiResponse<Unit?> {
+    fun postExamplePath1(
+        generatedType: Content,
+        explodeListQueryParam: List<String>?,
+        additionalHeaders: Map<String, String> = emptyMap()
+    ): ApiResponse<Unit?> {
         val httpUrl: HttpUrl = "$baseUrl/example-path-1"
             .toHttpUrl()
             .newBuilder()
             .queryParam("explode_list_query_param", explodeListQueryParam, true)
             .build()
 
-        val httpHeaders: Headers = Headers.Builder()
-            .build()
+        val headerBuilder = Headers.Builder()
+        additionalHeaders.forEach { headerBuilder.header(it.key, it.value) }
+        val httpHeaders: Headers = headerBuilder.build()
 
         val request: Request = Request.Builder()
             .url(httpUrl)
@@ -92,7 +101,8 @@ class ExamplePathClient(
     fun getExamplePath2PathParam(
         pathParam: String,
         queryParam2: Int?,
-        ifNoneMatch: String?
+        ifNoneMatch: String?,
+        additionalHeaders: Map<String, String> = emptyMap()
     ): ApiResponse<Content?> {
         val httpUrl: HttpUrl = "$baseUrl/example-path-2/{path_param}"
             .pathParam("{path_param}" to pathParam)
@@ -101,10 +111,11 @@ class ExamplePathClient(
             .queryParam("query_param2", queryParam2)
             .build()
 
-        val httpHeaders: Headers = Headers.Builder()
+        val headerBuilder = Headers.Builder()
             .header("If-None-Match", ifNoneMatch)
             .header("Accept", "application/json")
-            .build()
+        additionalHeaders.forEach { headerBuilder.header(it.key, it.value) }
+        val httpHeaders: Headers = headerBuilder.build()
 
         val request: Request = Request.Builder()
             .url(httpUrl)
@@ -126,7 +137,8 @@ class ExamplePathClient(
     fun headExamplePath2PathParam(
         pathParam: String,
         queryParam3: Boolean?,
-        ifNoneMatch: String?
+        ifNoneMatch: String?,
+        additionalHeaders: Map<String, String> = emptyMap()
     ): ApiResponse<Unit?> {
         val httpUrl: HttpUrl = "$baseUrl/example-path-2/{path_param}"
             .pathParam("{path_param}" to pathParam)
@@ -135,9 +147,10 @@ class ExamplePathClient(
             .queryParam("query_param3", queryParam3)
             .build()
 
-        val httpHeaders: Headers = Headers.Builder()
+        val headerBuilder = Headers.Builder()
             .header("If-None-Match", ifNoneMatch)
-            .build()
+        additionalHeaders.forEach { headerBuilder.header(it.key, it.value) }
+        val httpHeaders: Headers = headerBuilder.build()
 
         val request: Request = Request.Builder()
             .url(httpUrl)
@@ -158,7 +171,8 @@ class ExamplePathClient(
     fun putExamplePath2PathParam(
         firstModel: FirstModel,
         pathParam: String,
-        ifMatch: String
+        ifMatch: String,
+        additionalHeaders: Map<String, String> = emptyMap()
     ): ApiResponse<Unit?> {
         val httpUrl: HttpUrl = "$baseUrl/example-path-2/{path_param}"
             .pathParam("{path_param}" to pathParam)
@@ -166,9 +180,10 @@ class ExamplePathClient(
             .newBuilder()
             .build()
 
-        val httpHeaders: Headers = Headers.Builder()
+        val headerBuilder = Headers.Builder()
             .header("If-Match", ifMatch)
-            .build()
+        additionalHeaders.forEach { headerBuilder.header(it.key, it.value) }
+        val httpHeaders: Headers = headerBuilder.build()
 
         val request: Request = Request.Builder()
             .url(httpUrl)
@@ -198,7 +213,8 @@ class ExamplePathSubresourceClient(
         firstModel: FirstModel,
         csvListQueryParam: List<String>?,
         pathParam: String,
-        ifMatch: String
+        ifMatch: String,
+        additionalHeaders: Map<String, String> = emptyMap()
     ): ApiResponse<Unit?> {
         val httpUrl: HttpUrl = "$baseUrl/example-path-3/{path_param}/subresource"
             .pathParam("{path_param}" to pathParam)
@@ -207,9 +223,10 @@ class ExamplePathSubresourceClient(
             .queryParam("csv_list_query_param", csvListQueryParam, false)
             .build()
 
-        val httpHeaders: Headers = Headers.Builder()
+        val headerBuilder = Headers.Builder()
             .header("If-Match", ifMatch)
-            .build()
+        additionalHeaders.forEach { headerBuilder.header(it.key, it.value) }
+        val httpHeaders: Headers = headerBuilder.build()
 
         val request: Request = Request.Builder()
             .url(httpUrl)
diff --git a/src/test/resources/examples/okHttpClient/client/ApiService.kt b/src/test/resources/examples/okHttpClient/client/ApiService.kt
index d62e28a..a7597b3 100644
--- a/src/test/resources/examples/okHttpClient/client/ApiService.kt
+++ b/src/test/resources/examples/okHttpClient/client/ApiService.kt
@@ -12,6 +12,7 @@ import kotlin.String
 import kotlin.Suppress
 import kotlin.Unit
 import kotlin.collections.List
+import kotlin.collections.Map
 import kotlin.jvm.Throws
 
 /**
@@ -34,47 +35,56 @@ class ExamplePathService(
     private val apiClient: ExamplePathClient = ExamplePathClient(objectMapper, baseUrl, client)
 
     @Throws(ApiException::class)
-    fun getExamplePath1(explodeListQueryParam: List<String>?, queryParam2: Int?):
-        ApiResponse<QueryResult?> =
+    fun getExamplePath1(
+        explodeListQueryParam: List<String>?,
+        queryParam2: Int?,
+        additionalHeaders: Map<String, String> = emptyMap()
+    ): ApiResponse<QueryResult?> =
         withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
-            apiClient.getExamplePath1(explodeListQueryParam, queryParam2)
+            apiClient.getExamplePath1(explodeListQueryParam, queryParam2, additionalHeaders)
         }
 
     @Throws(ApiException::class)
-    fun postExamplePath1(generatedType: Content, explodeListQueryParam: List<String>?):
-        ApiResponse<Unit?> =
+    fun postExamplePath1(
+        generatedType: Content,
+        explodeListQueryParam: List<String>?,
+        additionalHeaders: Map<String, String> = emptyMap()
+    ): ApiResponse<Unit?> =
         withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
-            apiClient.postExamplePath1(generatedType, explodeListQueryParam)
+            apiClient.postExamplePath1(generatedType, explodeListQueryParam, additionalHeaders)
         }
 
     @Throws(ApiException::class)
     fun getExamplePath2PathParam(
         pathParam: String,
         queryParam2: Int?,
-        ifNoneMatch: String?
+        ifNoneMatch: String?,
+        additionalHeaders: Map<String, String> = emptyMap()
     ): ApiResponse<Content?> =
         withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
-            apiClient.getExamplePath2PathParam(pathParam, queryParam2, ifNoneMatch)
+            apiClient.getExamplePath2PathParam(pathParam, queryParam2, ifNoneMatch, additionalHeaders)
         }
 
     @Throws(ApiException::class)
     fun headExamplePath2PathParam(
         pathParam: String,
         queryParam3: Boolean?,
-        ifNoneMatch: String?
+        ifNoneMatch: String?,
+        additionalHeaders: Map<String, String> = emptyMap()
     ): ApiResponse<Unit?> =
         withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
-            apiClient.headExamplePath2PathParam(pathParam, queryParam3, ifNoneMatch)
+            apiClient.headExamplePath2PathParam(pathParam, queryParam3, ifNoneMatch, additionalHeaders)
         }
 
     @Throws(ApiException::class)
     fun putExamplePath2PathParam(
         firstModel: FirstModel,
         pathParam: String,
-        ifMatch: String
+        ifMatch: String,
+        additionalHeaders: Map<String, String> = emptyMap()
     ): ApiResponse<Unit?> =
         withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
-            apiClient.putExamplePath2PathParam(firstModel, pathParam, ifMatch)
+            apiClient.putExamplePath2PathParam(firstModel, pathParam, ifMatch, additionalHeaders)
         }
 }
 
@@ -106,9 +116,10 @@ class ExamplePathSubresourceService(
         firstModel: FirstModel,
         csvListQueryParam: List<String>?,
         pathParam: String,
-        ifMatch: String
+        ifMatch: String,
+        additionalHeaders: Map<String, String> = emptyMap()
     ): ApiResponse<Unit?> =
         withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
-            apiClient.putExamplePath3PathParamSubresource(firstModel, csvListQueryParam, pathParam, ifMatch)
+            apiClient.putExamplePath3PathParamSubresource(firstModel, csvListQueryParam, pathParam, ifMatch, additionalHeaders)
         }
 }
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiClient.kt b/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiClient.kt
index 007c8e1..f9ccf28 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiClient.kt
+++ b/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiClient.kt
@@ -12,6 +12,7 @@ import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
 import kotlin.collections.List
+import kotlin.collections.Map
 import kotlin.jvm.Throws
 
 @Suppress("unused")
@@ -30,7 +31,8 @@ class ExamplePathClient(
     fun getExamplePath1(
         explodeListQueryParam: List<String>?,
         queryParam2: Int?,
-        acceptHeader: String = "application/vnd.custom.media+xml"
+        acceptHeader: String = "application/vnd.custom.media+xml",
+        additionalHeaders: Map<String, String> = emptyMap()
     ): ApiResponse<QueryResult?> {
         val httpUrl: HttpUrl = "$baseUrl/example-path-1"
             .toHttpUrl()
@@ -39,9 +41,10 @@ class ExamplePathClient(
             .queryParam("query_param2", queryParam2)
             .build()
 
-        val httpHeaders: Headers = Headers.Builder()
+        val headerBuilder = Headers.Builder()
             .header("Accept", acceptHeader)
-            .build()
+        additionalHeaders.forEach { headerBuilder.header(it.key, it.value) }
+        val httpHeaders: Headers = headerBuilder.build()
 
         val request: Request = Request.Builder()
             .url(httpUrl)
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiService.kt b/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiService.kt
index 85f00ab..f2ba9c9 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiService.kt
+++ b/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiService.kt
@@ -8,6 +8,7 @@ import kotlin.Int
 import kotlin.String
 import kotlin.Suppress
 import kotlin.collections.List
+import kotlin.collections.Map
 import kotlin.jvm.Throws
 
 /**
@@ -33,9 +34,10 @@ class ExamplePathService(
     fun getExamplePath1(
         explodeListQueryParam: List<String>?,
         queryParam2: Int?,
-        acceptHeader: String = "application/vnd.custom.media+xml"
+        acceptHeader: String = "application/vnd.custom.media+xml",
+        additionalHeaders: Map<String, String> = emptyMap()
     ): ApiResponse<QueryResult?> =
         withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
-            apiClient.getExamplePath1(explodeListQueryParam, queryParam2, acceptHeader)
+            apiClient.getExamplePath1(explodeListQueryParam, queryParam2, acceptHeader, additionalHeaders)
         }
 }