Repository: cjbooms/fabrikt
Pull request #86 (2021-11-24 21:07:29)

Permitting the use of explicit Accept header in the generated clients
Permitting the use of explicit Accept header in the generated clients


Patch:
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 9422de7..3da6ef1 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpEnhancedClientGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpEnhancedClientGenerator.kt
@@ -64,7 +64,10 @@ class OkHttpEnhancedClientGenerator(
                                 .defaultValue("%S", operation.getPrimaryContentMediaTypeKey())
                                 .build(),
                             operation,
-                        ) { it.hasMultipleContentMediaTypes() == true }
+                        ) {
+                            it.hasMultipleContentMediaTypes() == true &&
+                                !operation.parameters.any { header -> header.name == "Accept" }
+                        }
                         .addParameter(
                             ParameterSpec.builder(
                                 ADDITIONAL_HEADERS_PARAMETER_NAME,
@@ -191,11 +194,15 @@ class Resilience4jClientOperationStatement(
     }
 
     private fun CodeBlock.Builder.addClientCallStatement(): CodeBlock.Builder {
+        var isAcceptSet = false
         val params = mutableListOf(
             operation.requestBody.toBodyParameterSpec(packages.base),
-            operation.parameters.map { it.toParameterSpec(packages.base) }
+            operation.parameters.map { parameter ->
+                if (parameter.name == "Accept") isAcceptSet = true
+                parameter.toParameterSpec(packages.base)
+            }
         )
-        operation.firstResponse()?.let {
+        if (!isAcceptSet) operation.firstResponse()?.let {
             if (it.hasMultipleContentMediaTypes()) {
                 params.add(listOf(ParameterSpec.builder(ACCEPT_HEADER_VARIABLE_NAME, String::class).build()))
             }
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 1f1dcf7..c229d2c 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpSimpleClientGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/client/OkHttpSimpleClientGenerator.kt
@@ -65,7 +65,10 @@ class OkHttpSimpleClientGenerator(
                                 .defaultValue("%S", operation.getPrimaryContentMediaTypeKey())
                                 .build(),
                             operation,
-                        ) { it.hasMultipleContentMediaTypes() == true }
+                        ) {
+                            it.hasMultipleContentMediaTypes() == true &&
+                                !operation.parameters.any { header -> header.name == "Accept" }
+                        }
                         .addParameter(
                             ParameterSpec.builder(
                                 ADDITIONAL_HEADERS_PARAMETER_NAME,
@@ -187,15 +190,27 @@ data class SimpleClientOperationStatement(
 
     private fun CodeBlock.Builder.addHeaderParamStatement(): CodeBlock.Builder {
         this.add("\nval headerBuilder = Headers.Builder()")
+        var isAcceptSet = false
         operation.getHeaderParams().map {
-            this.add("\n.%T(%S, %N)", "header".toClassName(packages.client), it.name, it.name.toKCodeName())
+            if (it.name == "Accept") isAcceptSet = true
+            val typeInfo = KotlinTypeInfo.from(it.schema)
+            this.add(
+                "\n.%T(%S, %L)", "header".toClassName(packages.client),
+                it.name,
+                it.name.toKCodeName() + if (typeInfo is KotlinTypeInfo.Enum) "?.value" else ""
+            )
         }
 
-        operation.firstResponse()?.let {
+        if (!isAcceptSet) operation.firstResponse()?.let {
             if (it.hasMultipleContentMediaTypes()) {
                 this.add("\n.%T(%S, %L)", "header".toClassName(packages.client), "Accept", ACCEPT_HEADER_VARIABLE_NAME)
             } else {
-                this.add("\n.%T(%S, %S)", "header".toClassName(packages.client), "Accept", operation.getPrimaryContentMediaTypeKey())
+                this.add(
+                    "\n.%T(%S, %S)",
+                    "header".toClassName(packages.client),
+                    "Accept",
+                    operation.getPrimaryContentMediaTypeKey()
+                )
             }
         }
         this.add("\nadditionalHeaders.forEach { headerBuilder.header(it.key, it.value) }")


Test patch:
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/OkHttpClientGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/OkHttpClientGeneratorTest.kt
index a7bc03e..e1128a0 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/OkHttpClientGeneratorTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/OkHttpClientGeneratorTest.kt
@@ -2,6 +2,7 @@ package com.cjbooms.fabrikt.generators
 
 import com.cjbooms.fabrikt.cli.ClientCodeGenOptionType
 import com.cjbooms.fabrikt.cli.CodeGenerationType
+import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType
 import com.cjbooms.fabrikt.configurations.Packages
 import com.cjbooms.fabrikt.generators.client.OkHttpEnhancedClientGenerator
 import com.cjbooms.fabrikt.generators.client.OkHttpSimpleClientGenerator
@@ -32,7 +33,7 @@ class OkHttpClientGeneratorTest {
 
     @BeforeEach
     fun init() {
-        MutableSettings.updateSettings(setOf(CodeGenerationType.CLIENT), emptySet(), emptySet())
+        MutableSettings.updateSettings(setOf(CodeGenerationType.CLIENT), setOf(ModelCodeGenOptionType.X_EXTENSIBLE_ENUMS), emptySet())
     }
 
     @ParameterizedTest
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/api.yaml b/src/test/resources/examples/okHttpClientMultiMediaType/api.yaml
index 1dd5aca..3ab3781 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/api.yaml
+++ b/src/test/resources/examples/okHttpClientMultiMediaType/api.yaml
@@ -9,6 +9,26 @@ paths:
       parameters:
         - $ref: "#/components/parameters/ListQueryParamExploded"
         - $ref: "#/components/parameters/QueryParam2"
+      responses:
+        200:
+          description: "successful operation"
+          headers:
+            Cache-Control:
+              $ref: "#/components/headers/CacheControl"
+          content:
+            application/vnd.custom.media+xml:
+              schema:
+                $ref: "#/components/schemas/QueryResult"
+            application/vnd.custom.media+json:
+              schema:
+                $ref: "#/components/schemas/QueryResult"  
+  /example-path-2:
+    get:
+      summary: "GET example path 1"
+      parameters:
+        - $ref: "#/components/parameters/ListQueryParamExploded"
+        - $ref: "#/components/parameters/QueryParam2"
+        - $ref: "#/components/parameters/Accept"
       responses:
         200:
           description: "successful operation"
@@ -41,6 +61,14 @@ components:
       schema:
         type: integer
       required: false
+      
+    Accept:
+      name: Accept
+      in: header
+      required: false
+      description: the content type accepted by the client
+      schema:
+        $ref: '#/components/schemas/ContentType'
 
   headers:
     CacheControl:
@@ -50,6 +78,11 @@ components:
       example: "must-revalidate, max-age=5"
 
   schemas:
+    ContentType:
+      type: string
+      x-extensible-enum:
+        - application/json
+        - application/vnd.custom.media+json
     QueryResult:
       type: "object"
       required:
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiClient.kt b/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiClient.kt
index 47251d1..16b9931 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiClient.kt
+++ b/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiClient.kt
@@ -2,6 +2,7 @@ package examples.okHttpClientMultiMediaType.client
 
 import com.fasterxml.jackson.databind.ObjectMapper
 import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
+import examples.okHttpClientMultiMediaType.models.ContentType
 import examples.okHttpClientMultiMediaType.models.QueryResult
 import okhttp3.Headers
 import okhttp3.HttpUrl
@@ -55,3 +56,45 @@ class ExamplePath1Client(
         return request.execute(client, objectMapper, jacksonTypeRef())
     }
 }
+
+@Suppress("unused")
+class ExamplePath2Client(
+    private val objectMapper: ObjectMapper,
+    private val baseUrl: String,
+    private val client: OkHttpClient
+) {
+    /**
+     * GET example path 1
+     *
+     * @param explodeListQueryParam
+     * @param queryParam2
+     * @param accept the content type accepted by the client
+     */
+    @Throws(ApiException::class)
+    fun getExamplePath2(
+        explodeListQueryParam: List<String>?,
+        queryParam2: Int?,
+        accept: ContentType?,
+        additionalHeaders: Map<String, String> = emptyMap()
+    ): ApiResponse<QueryResult?> {
+        val httpUrl: HttpUrl = "$baseUrl/example-path-2"
+            .toHttpUrl()
+            .newBuilder()
+            .queryParam("explode_list_query_param", explodeListQueryParam, true)
+            .queryParam("query_param2", queryParam2)
+            .build()
+
+        val headerBuilder = Headers.Builder()
+            .header("Accept", accept?.value)
+        additionalHeaders.forEach { headerBuilder.header(it.key, it.value) }
+        val httpHeaders: Headers = headerBuilder.build()
+
+        val request: Request = Request.Builder()
+            .url(httpUrl)
+            .headers(httpHeaders)
+            .get()
+            .build()
+
+        return request.execute(client, objectMapper, jacksonTypeRef())
+    }
+}
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiService.kt b/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiService.kt
index 8e7739f..c914ff8 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiService.kt
+++ b/src/test/resources/examples/okHttpClientMultiMediaType/client/ApiService.kt
@@ -1,6 +1,7 @@
 package examples.okHttpClientMultiMediaType.client
 
 import com.fasterxml.jackson.databind.ObjectMapper
+import examples.okHttpClientMultiMediaType.models.ContentType
 import examples.okHttpClientMultiMediaType.models.QueryResult
 import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry
 import okhttp3.OkHttpClient
@@ -41,3 +42,34 @@ class ExamplePath1Service(
             apiClient.getExamplePath1(explodeListQueryParam, queryParam2, acceptHeader, additionalHeaders)
         }
 }
+
+/**
+ * The circuit breaker registry should have the proper configuration to correctly action on circuit
+ * breaker transitions based on the client exceptions [ApiClientException], [ApiServerException] and
+ * [IOException].
+ *
+ * @see ApiClientException
+ * @see ApiServerException
+ */
+@Suppress("unused")
+class ExamplePath2Service(
+    private val circuitBreakerRegistry: CircuitBreakerRegistry,
+    objectMapper: ObjectMapper,
+    baseUrl: String,
+    client: OkHttpClient
+) {
+    var circuitBreakerName: String = "examplePath2Client"
+
+    private val apiClient: ExamplePath2Client = ExamplePath2Client(objectMapper, baseUrl, client)
+
+    @Throws(ApiException::class)
+    fun getExamplePath2(
+        explodeListQueryParam: List<String>?,
+        queryParam2: Int?,
+        accept: ContentType?,
+        additionalHeaders: Map<String, String> = emptyMap()
+    ): ApiResponse<QueryResult?> =
+        withCircuitBreaker(circuitBreakerRegistry, circuitBreakerName) {
+            apiClient.getExamplePath2(explodeListQueryParam, queryParam2, accept, additionalHeaders)
+        }
+}
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/models/Models.kt b/src/test/resources/examples/okHttpClientMultiMediaType/models/Models.kt
index be2cfc2..3d2cbfc 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/models/Models.kt
+++ b/src/test/resources/examples/okHttpClientMultiMediaType/models/Models.kt
@@ -13,6 +13,15 @@ import kotlin.Int
 import kotlin.String
 import kotlin.collections.List
 
+enum class ContentType(
+    @JsonValue
+    val value: String
+) {
+    APPLICATION_JSON("application/json"),
+
+    APPLICATION_VND_CUSTOM_MEDIA_JSON("application/vnd.custom.media+json");
+}
+
 data class QueryResult(
     @param:JsonProperty("items")
     @get:JsonProperty("items")