Skip to content

Commit 568663a

Browse files
committed
Fix handling of Top Level Operation Headers
1 parent 7967e72 commit 568663a

File tree

4 files changed

+54
-15
lines changed

4 files changed

+54
-15
lines changed

plugin/src/main/java/com/yelp/codegen/KotlinGenerator.kt

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ class KotlinGenerator : SharedCodegen() {
2828
* This number represents the version of the kotlin template
2929
* Please note that is independent from the Plugin version
3030
*/
31-
@JvmStatic val VERSION = 11
31+
@JvmStatic val VERSION = 12
32+
33+
// Vendor Extension use to generate the list of top level headers
34+
const val HAS_OPERATION_HEADERS = "hasOperationHeaders"
35+
const val OPERATION_HEADERS = "operationHeaders"
3236
}
3337

3438
private val apiDocPath = "docs/"
@@ -404,7 +408,6 @@ class KotlinGenerator : SharedCodegen() {
404408

405409
codegenOperation.imports.add("retrofit2.http.Headers")
406410
codegenOperation.vendorExtensions[X_OPERATION_ID] = operation?.operationId
407-
408411
getHeadersToIgnore().forEach { headerName ->
409412
ignoreHeaderParameter(headerName, codegenOperation)
410413
}
@@ -413,6 +416,7 @@ class KotlinGenerator : SharedCodegen() {
413416
if (!basePath.isNullOrBlank()) {
414417
codegenOperation.path = codegenOperation.path.removePrefix("/")
415418
}
419+
processTopLevelHeaders(codegenOperation)
416420
return codegenOperation
417421
}
418422

@@ -451,4 +455,30 @@ class KotlinGenerator : SharedCodegen() {
451455
override fun removeNonNameElementToCamelCase(name: String?): String {
452456
return super.removeNonNameElementToCamelCase(name, "[-_:;#\\[\\]]")
453457
}
458+
459+
/**
460+
* Function to check if there are Headers that should be applied at the Top level on Retrofit
461+
* with the @Headers annotation. This method will populate the `hasOperationHeaders` and `operationHeaders`
462+
* vendor extensions to support the mustache template.
463+
*/
464+
internal fun processTopLevelHeaders(operation: CodegenOperation) {
465+
val topLevelHeaders = mutableListOf<Pair<String, String>>()
466+
467+
// Send the X-Operation-Id header only if a custom operation ID was set.
468+
val operationId = operation.vendorExtensions[X_OPERATION_ID] as String?
469+
if (!operationId.isNullOrBlank()) {
470+
topLevelHeaders.add(HEADER_X_OPERATION_ID to operationId)
471+
}
472+
473+
// Send the Content-Type header for the first `consume` mediaType specified.
474+
val firstContentType = operation.consumes?.firstOrNull()
475+
if (operation.formParams.isNullOrEmpty() && firstContentType != null) {
476+
firstContentType["mediaType"]?.let {
477+
topLevelHeaders.add(HEADER_CONTENT_TYPE to it)
478+
}
479+
}
480+
481+
operation.vendorExtensions[HAS_OPERATION_HEADERS] = topLevelHeaders.isNotEmpty()
482+
operation.vendorExtensions[OPERATION_HEADERS] = topLevelHeaders
483+
}
454484
}

plugin/src/main/java/com/yelp/codegen/SharedCodegen.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,16 @@ const val ARTIFACT_ID = "artifact_id"
3232
const val GROUP_ID = "group_id"
3333
const val HEADERS_TO_IGNORE = "headers_to_ignore"
3434

35+
// Vendor Extensions Names
3536
internal const val X_NULLABLE = "x-nullable"
3637
internal const val X_MODEL = "x-model"
3738
internal const val X_OPERATION_ID = "x-operation-id"
3839
internal const val X_UNSAFE_OPERATION = "x-unsafe-operation"
3940

41+
// Headers Names
42+
internal const val HEADER_X_OPERATION_ID = "X-Operation-Id"
43+
internal const val HEADER_CONTENT_TYPE = "Content-Type"
44+
4045
abstract class SharedCodegen : DefaultCodegen(), CodegenConfig {
4146

4247
// Reference to the Swagger Specs

plugin/src/main/resources/kotlin/retrofit2/api.mustache

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,12 @@ interface {{classname}} {
2525
{{#isMultipart}}@retrofit2.http.Multipart{{/isMultipart}}{{^isMultipart}}@retrofit2.http.FormUrlEncoded{{/isMultipart}}
2626
{{/-first}}
2727
{{/formParams}}
28-
{{^formParams}}
29-
{{#prioritizedContentTypes}}
30-
{{#-first}}
31-
@Headers({
32-
"Content-Type:{{{mediaType}}}"
33-
})
34-
{{/-first}}
35-
{{/prioritizedContentTypes}}
36-
{{/formParams}}
37-
@Headers({{#vendorExtensions.x-operation-id}}{{{newline}}} "X-Operation-ID: {{vendorExtensions.x-operation-id}}"{{/vendorExtensions.x-operation-id}}{{^formParams}}{{#prioritizedContentTypes}}{{#-first}},
38-
"Content-Type:{{{mediaType}}}"{{/-first}}{{/prioritizedContentTypes}}{{/formParams}}
39-
)
40-
28+
{{#vendorExtensions.hasOperationHeaders}}
29+
@Headers(
30+
{{#vendorExtensions.operationHeaders}}"{{first}}: {{second}}"{{^-last}},
31+
{{/-last}}{{#-last}}
32+
){{/-last}}{{/vendorExtensions.operationHeaders}}
33+
{{/vendorExtensions.hasOperationHeaders}}
4134
@{{httpMethod}}("{{{path}}}"){{#vendorExtensions.x-unsafe-operation}}{{#isDeprecated}}
4235
@Deprecated(message = "Deprecated and unsafe to use"){{/isDeprecated}}{{^isDeprecated}}
4336
@Deprecated(message = "Unsafe to use"){{/isDeprecated}}

plugin/src/test/java/com/yelp/codegen/KotlinGeneratorTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.yelp.codegen
22

33
import io.swagger.codegen.CodegenModel
4+
import io.swagger.codegen.CodegenOperation
45
import io.swagger.codegen.CodegenProperty
56
import io.swagger.models.Info
67
import io.swagger.models.Operation
@@ -353,4 +354,14 @@ class KotlinGeneratorTest {
353354
assertEquals("42.0.0", swagger.info.version)
354355
assertEquals("/v2", generator.basePath)
355356
}
357+
358+
@Test
359+
fun processTopLevelHeaders_withNoHeaders() {
360+
val generator = KotlinGenerator()
361+
val operation = CodegenOperation()
362+
363+
generator.processTopLevelHeaders(operation)
364+
365+
assertEquals(false, operation.vendorExtensions["hasOperationHeaders"])
366+
}
356367
}

0 commit comments

Comments
 (0)