Skip to content

Commit

Permalink
Merge pull request #52 from Yelp/fix-45-empty-header-annotation
Browse files Browse the repository at this point in the history
Fix handling of Top Level Operation Headers
  • Loading branch information
cortinico authored Jul 30, 2019
2 parents 08975a0 + 7b54ad9 commit 88ac0ef
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@file:Suppress("Unused")

object PublishingVersions {
const val PLUGIN_VERSION = "1.1.1"
const val PLUGIN_VERSION = "1.2.0-SNAPSHOT"
const val PLUGIN_GROUP = "com.yelp.codegen"
const val PLUGIN_ARTIFACT = "plugin"
}
34 changes: 32 additions & 2 deletions plugin/src/main/java/com/yelp/codegen/KotlinGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ class KotlinGenerator : SharedCodegen() {
* This number represents the version of the kotlin template
* Please note that is independent from the Plugin version
*/
@JvmStatic val VERSION = 11
@JvmStatic val VERSION = 12

// Vendor Extension use to generate the list of top level headers
const val HAS_OPERATION_HEADERS = "hasOperationHeaders"
const val OPERATION_HEADERS = "operationHeaders"
}

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

codegenOperation.imports.add("retrofit2.http.Headers")
codegenOperation.vendorExtensions[X_OPERATION_ID] = operation?.operationId

getHeadersToIgnore().forEach { headerName ->
ignoreHeaderParameter(headerName, codegenOperation)
}
Expand All @@ -413,6 +416,7 @@ class KotlinGenerator : SharedCodegen() {
if (!basePath.isNullOrBlank()) {
codegenOperation.path = codegenOperation.path.removePrefix("/")
}
processTopLevelHeaders(codegenOperation)
return codegenOperation
}

Expand Down Expand Up @@ -451,4 +455,30 @@ class KotlinGenerator : SharedCodegen() {
override fun removeNonNameElementToCamelCase(name: String?): String {
return super.removeNonNameElementToCamelCase(name, "[-_:;#\\[\\]]")
}

/**
* Function to check if there are Headers that should be applied at the Top level on Retrofit
* with the @Headers annotation. This method will populate the `hasOperationHeaders` and `operationHeaders`
* vendor extensions to support the mustache template.
*/
internal fun processTopLevelHeaders(operation: CodegenOperation) {
val topLevelHeaders = mutableListOf<Pair<String, String>>()

// Send the X-Operation-Id header only if a custom operation ID was set.
val operationId = operation.vendorExtensions[X_OPERATION_ID] as String?
if (!operationId.isNullOrBlank()) {
topLevelHeaders.add(HEADER_X_OPERATION_ID to operationId)
}

// Send the Content-Type header for the first `consume` mediaType specified.
val firstContentType = operation.consumes?.firstOrNull()
if (operation.formParams.isNullOrEmpty() && firstContentType != null) {
firstContentType["mediaType"]?.let {
topLevelHeaders.add(HEADER_CONTENT_TYPE to it)
}
}

operation.vendorExtensions[HAS_OPERATION_HEADERS] = topLevelHeaders.isNotEmpty()
operation.vendorExtensions[OPERATION_HEADERS] = topLevelHeaders
}
}
5 changes: 5 additions & 0 deletions plugin/src/main/java/com/yelp/codegen/SharedCodegen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,16 @@ const val ARTIFACT_ID = "artifact_id"
const val GROUP_ID = "group_id"
const val HEADERS_TO_IGNORE = "headers_to_ignore"

// Vendor Extensions Names
internal const val X_NULLABLE = "x-nullable"
internal const val X_MODEL = "x-model"
internal const val X_OPERATION_ID = "x-operation-id"
internal const val X_UNSAFE_OPERATION = "x-unsafe-operation"

// Headers Names
internal const val HEADER_X_OPERATION_ID = "X-Operation-Id"
internal const val HEADER_CONTENT_TYPE = "Content-Type"

abstract class SharedCodegen : DefaultCodegen(), CodegenConfig {

// Reference to the Swagger Specs
Expand Down
19 changes: 6 additions & 13 deletions plugin/src/main/resources/kotlin/retrofit2/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,12 @@ interface {{classname}} {
{{#isMultipart}}@retrofit2.http.Multipart{{/isMultipart}}{{^isMultipart}}@retrofit2.http.FormUrlEncoded{{/isMultipart}}
{{/-first}}
{{/formParams}}
{{^formParams}}
{{#prioritizedContentTypes}}
{{#-first}}
@Headers({
"Content-Type:{{{mediaType}}}"
})
{{/-first}}
{{/prioritizedContentTypes}}
{{/formParams}}
@Headers({{#vendorExtensions.x-operation-id}}{{{newline}}} "X-Operation-ID: {{vendorExtensions.x-operation-id}}"{{/vendorExtensions.x-operation-id}}{{^formParams}}{{#prioritizedContentTypes}}{{#-first}},
"Content-Type:{{{mediaType}}}"{{/-first}}{{/prioritizedContentTypes}}{{/formParams}}
)

{{#vendorExtensions.hasOperationHeaders}}
@Headers(
{{#vendorExtensions.operationHeaders}}"{{first}}: {{second}}"{{^-last}},
{{/-last}}{{#-last}}
){{/-last}}{{/vendorExtensions.operationHeaders}}
{{/vendorExtensions.hasOperationHeaders}}
@{{httpMethod}}("{{{path}}}"){{#vendorExtensions.x-unsafe-operation}}{{#isDeprecated}}
@Deprecated(message = "Deprecated and unsafe to use"){{/isDeprecated}}{{^isDeprecated}}
@Deprecated(message = "Unsafe to use"){{/isDeprecated}}
Expand Down
87 changes: 87 additions & 0 deletions plugin/src/test/java/com/yelp/codegen/KotlinGeneratorTest.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.yelp.codegen

import io.swagger.codegen.CodegenModel
import io.swagger.codegen.CodegenOperation
import io.swagger.codegen.CodegenParameter
import io.swagger.codegen.CodegenProperty
import io.swagger.models.Info
import io.swagger.models.Operation
Expand Down Expand Up @@ -353,4 +355,89 @@ class KotlinGeneratorTest {
assertEquals("42.0.0", swagger.info.version)
assertEquals("/v2", generator.basePath)
}

@Test
fun processTopLevelHeaders_withNoHeaders_hasOperationHeadersIsFalse() {
val generator = KotlinGenerator()
val operation = CodegenOperation()
operation.vendorExtensions = mutableMapOf()

generator.processTopLevelHeaders(operation)

assertEquals(false, operation.vendorExtensions["hasOperationHeaders"])
}

@Test
fun processTopLevelHeaders_withOperationId_hasXOperationIdHeader() {
val testOperationId = "aTestOperationId"
val generator = KotlinGenerator()
val operation = CodegenOperation()
operation.vendorExtensions = mutableMapOf(X_OPERATION_ID to (testOperationId as Any))

generator.processTopLevelHeaders(operation)

assertEquals(true, operation.vendorExtensions["hasOperationHeaders"])
val headerMap = operation.vendorExtensions["operationHeaders"] as List<*>
assertEquals(1, headerMap.size)
val firstPair = headerMap[0] as Pair<*, *>
assertEquals(HEADER_X_OPERATION_ID, firstPair.first as String)
assertEquals(testOperationId, firstPair.second as String)
}

@Test
fun processTopLevelHeaders_withConsumes_hasContentTypeHeader() {
val generator = KotlinGenerator()
val operation = CodegenOperation()
operation.vendorExtensions = mutableMapOf()
operation.consumes = listOf(
mapOf("mediaType" to "application/json")
)

generator.processTopLevelHeaders(operation)

assertEquals(true, operation.vendorExtensions["hasOperationHeaders"])
val headerMap = operation.vendorExtensions["operationHeaders"] as List<*>
assertEquals(1, headerMap.size)
val firstPair = headerMap[0] as Pair<*, *>
assertEquals(HEADER_CONTENT_TYPE, firstPair.first as String)
assertEquals("application/json", firstPair.second as String)
}

@Test
fun processTopLevelHeaders_withFormParams_hasNoContentTypeHeader() {
val generator = KotlinGenerator()
val operation = CodegenOperation()
operation.vendorExtensions = mutableMapOf()
operation.formParams = listOf(CodegenParameter())
operation.consumes = listOf(
mapOf("mediaType" to "application/json")
)

generator.processTopLevelHeaders(operation)

assertEquals(false, operation.vendorExtensions["hasOperationHeaders"])
}

@Test
fun processTopLevelHeaders_withConsumesAndOperationId_hasTwoHeaders() {
val testOperationId = "aTestOperationId"
val generator = KotlinGenerator()
val operation = CodegenOperation()
operation.vendorExtensions = mutableMapOf(X_OPERATION_ID to (testOperationId as Any))
operation.consumes = listOf(
mapOf("mediaType" to "application/json")
)

generator.processTopLevelHeaders(operation)

assertEquals(true, operation.vendorExtensions["hasOperationHeaders"])
val headerMap = operation.vendorExtensions["operationHeaders"] as List<*>
assertEquals(2, headerMap.size)
val firstPair = headerMap[0] as Pair<*, *>
assertEquals(HEADER_X_OPERATION_ID, firstPair.first as String)
assertEquals(testOperationId, firstPair.second as String)
val secondPair = headerMap[1] as Pair<*, *>
assertEquals(HEADER_CONTENT_TYPE, secondPair.first as String)
assertEquals("application/json", secondPair.second as String)
}
}
2 changes: 1 addition & 1 deletion samples/generated-code/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildscript {
dependencies {
classpath "com.android.tools.build:gradle:3.4.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.41"
classpath "com.yelp.codegen:plugin:1.1.1"
classpath "com.yelp.codegen:plugin:1.2.0-SNAPSHOT"
}
}

Expand Down
2 changes: 1 addition & 1 deletion samples/groovy-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildscript {
dependencies {
classpath "com.android.tools.build:gradle:3.4.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.41"
classpath "com.yelp.codegen:plugin:1.1.1"
classpath "com.yelp.codegen:plugin:1.2.0-SNAPSHOT"
}
}

Expand Down
2 changes: 1 addition & 1 deletion samples/junit-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ buildscript {
dependencies {
classpath "com.android.tools.build:gradle:3.4.2"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.41"
classpath "com.yelp.codegen:plugin:1.1.1"
classpath "com.yelp.codegen:plugin:1.2.0-SNAPSHOT"
classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.0.0-RC16"
}
}
Expand Down
2 changes: 1 addition & 1 deletion samples/kotlin-android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
plugins {
id("com.android.library") version "3.4.2"
kotlin("android") version "1.3.41"
id("com.yelp.codegen.plugin") version "1.1.1"
id("com.yelp.codegen.plugin") version "1.2.0-SNAPSHOT"
}

android {
Expand Down

0 comments on commit 88ac0ef

Please sign in to comment.