Skip to content

Commit 88ac0ef

Browse files
authored
Merge pull request #52 from Yelp/fix-45-empty-header-annotation
Fix handling of Top Level Operation Headers
2 parents 08975a0 + 7b54ad9 commit 88ac0ef

File tree

9 files changed

+135
-20
lines changed

9 files changed

+135
-20
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@file:Suppress("Unused")
22

33
object PublishingVersions {
4-
const val PLUGIN_VERSION = "1.1.1"
4+
const val PLUGIN_VERSION = "1.2.0-SNAPSHOT"
55
const val PLUGIN_GROUP = "com.yelp.codegen"
66
const val PLUGIN_ARTIFACT = "plugin"
77
}

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: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.yelp.codegen
22

33
import io.swagger.codegen.CodegenModel
4+
import io.swagger.codegen.CodegenOperation
5+
import io.swagger.codegen.CodegenParameter
46
import io.swagger.codegen.CodegenProperty
57
import io.swagger.models.Info
68
import io.swagger.models.Operation
@@ -353,4 +355,89 @@ class KotlinGeneratorTest {
353355
assertEquals("42.0.0", swagger.info.version)
354356
assertEquals("/v2", generator.basePath)
355357
}
358+
359+
@Test
360+
fun processTopLevelHeaders_withNoHeaders_hasOperationHeadersIsFalse() {
361+
val generator = KotlinGenerator()
362+
val operation = CodegenOperation()
363+
operation.vendorExtensions = mutableMapOf()
364+
365+
generator.processTopLevelHeaders(operation)
366+
367+
assertEquals(false, operation.vendorExtensions["hasOperationHeaders"])
368+
}
369+
370+
@Test
371+
fun processTopLevelHeaders_withOperationId_hasXOperationIdHeader() {
372+
val testOperationId = "aTestOperationId"
373+
val generator = KotlinGenerator()
374+
val operation = CodegenOperation()
375+
operation.vendorExtensions = mutableMapOf(X_OPERATION_ID to (testOperationId as Any))
376+
377+
generator.processTopLevelHeaders(operation)
378+
379+
assertEquals(true, operation.vendorExtensions["hasOperationHeaders"])
380+
val headerMap = operation.vendorExtensions["operationHeaders"] as List<*>
381+
assertEquals(1, headerMap.size)
382+
val firstPair = headerMap[0] as Pair<*, *>
383+
assertEquals(HEADER_X_OPERATION_ID, firstPair.first as String)
384+
assertEquals(testOperationId, firstPair.second as String)
385+
}
386+
387+
@Test
388+
fun processTopLevelHeaders_withConsumes_hasContentTypeHeader() {
389+
val generator = KotlinGenerator()
390+
val operation = CodegenOperation()
391+
operation.vendorExtensions = mutableMapOf()
392+
operation.consumes = listOf(
393+
mapOf("mediaType" to "application/json")
394+
)
395+
396+
generator.processTopLevelHeaders(operation)
397+
398+
assertEquals(true, operation.vendorExtensions["hasOperationHeaders"])
399+
val headerMap = operation.vendorExtensions["operationHeaders"] as List<*>
400+
assertEquals(1, headerMap.size)
401+
val firstPair = headerMap[0] as Pair<*, *>
402+
assertEquals(HEADER_CONTENT_TYPE, firstPair.first as String)
403+
assertEquals("application/json", firstPair.second as String)
404+
}
405+
406+
@Test
407+
fun processTopLevelHeaders_withFormParams_hasNoContentTypeHeader() {
408+
val generator = KotlinGenerator()
409+
val operation = CodegenOperation()
410+
operation.vendorExtensions = mutableMapOf()
411+
operation.formParams = listOf(CodegenParameter())
412+
operation.consumes = listOf(
413+
mapOf("mediaType" to "application/json")
414+
)
415+
416+
generator.processTopLevelHeaders(operation)
417+
418+
assertEquals(false, operation.vendorExtensions["hasOperationHeaders"])
419+
}
420+
421+
@Test
422+
fun processTopLevelHeaders_withConsumesAndOperationId_hasTwoHeaders() {
423+
val testOperationId = "aTestOperationId"
424+
val generator = KotlinGenerator()
425+
val operation = CodegenOperation()
426+
operation.vendorExtensions = mutableMapOf(X_OPERATION_ID to (testOperationId as Any))
427+
operation.consumes = listOf(
428+
mapOf("mediaType" to "application/json")
429+
)
430+
431+
generator.processTopLevelHeaders(operation)
432+
433+
assertEquals(true, operation.vendorExtensions["hasOperationHeaders"])
434+
val headerMap = operation.vendorExtensions["operationHeaders"] as List<*>
435+
assertEquals(2, headerMap.size)
436+
val firstPair = headerMap[0] as Pair<*, *>
437+
assertEquals(HEADER_X_OPERATION_ID, firstPair.first as String)
438+
assertEquals(testOperationId, firstPair.second as String)
439+
val secondPair = headerMap[1] as Pair<*, *>
440+
assertEquals(HEADER_CONTENT_TYPE, secondPair.first as String)
441+
assertEquals("application/json", secondPair.second as String)
442+
}
356443
}

samples/generated-code/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ buildscript {
1010
dependencies {
1111
classpath "com.android.tools.build:gradle:3.4.2"
1212
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.41"
13-
classpath "com.yelp.codegen:plugin:1.1.1"
13+
classpath "com.yelp.codegen:plugin:1.2.0-SNAPSHOT"
1414
}
1515
}
1616

samples/groovy-android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ buildscript {
1010
dependencies {
1111
classpath "com.android.tools.build:gradle:3.4.2"
1212
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.41"
13-
classpath "com.yelp.codegen:plugin:1.1.1"
13+
classpath "com.yelp.codegen:plugin:1.2.0-SNAPSHOT"
1414
}
1515
}
1616

samples/junit-tests/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ buildscript {
1010
dependencies {
1111
classpath "com.android.tools.build:gradle:3.4.2"
1212
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.41"
13-
classpath "com.yelp.codegen:plugin:1.1.1"
13+
classpath "com.yelp.codegen:plugin:1.2.0-SNAPSHOT"
1414
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.0.0-RC16"
1515
}
1616
}

samples/kotlin-android/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
plugins {
22
id("com.android.library") version "3.4.2"
33
kotlin("android") version "1.3.41"
4-
id("com.yelp.codegen.plugin") version "1.1.1"
4+
id("com.yelp.codegen.plugin") version "1.2.0-SNAPSHOT"
55
}
66

77
android {

0 commit comments

Comments
 (0)