Skip to content

Commit

Permalink
Kotlin Coroutines Support
Browse files Browse the repository at this point in the history
  • Loading branch information
cortinico committed May 24, 2019
1 parent 940c5a4 commit 7ec9866
Show file tree
Hide file tree
Showing 25 changed files with 482 additions and 8 deletions.
14 changes: 14 additions & 0 deletions plugin/src/main/java/com/yelp/codegen/KotlinCoroutineGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.yelp.codegen

open class KotlinCoroutineGenerator : KotlinGenerator() {

init {
templateDir = "kotlin-coroutines"
}

override fun getName() = "kotlin-coroutines"

override fun wrapResponseType(imports: MutableSet<String>, responsePrimitiveType: String) = responsePrimitiveType

override fun getNoResponseType(imports: MutableSet<String>) = "Unit"
}
31 changes: 23 additions & 8 deletions plugin/src/main/java/com/yelp/codegen/KotlinGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import io.swagger.models.Swagger
import io.swagger.models.properties.Property
import java.io.File

class KotlinGenerator : SharedCodegen() {
open class KotlinGenerator : SharedCodegen() {

companion object {
/**
Expand Down Expand Up @@ -377,19 +377,16 @@ class KotlinGenerator : SharedCodegen() {
}
}

when {
codegenOperation.returnType = when {
codegenOperation.isResponseFile -> {
codegenOperation.returnType = "Single<ResponseBody>"
codegenOperation.imports.add("okhttp3.ResponseBody")
codegenOperation.imports.add("io.reactivex.Single")
wrapResponseType(codegenOperation.imports, "ResponseBody")
}
codegenOperation.returnType == null -> {
codegenOperation.returnType = "Completable"
codegenOperation.imports.add("io.reactivex.Completable")
getNoResponseType(codegenOperation.imports)
}
else -> {
codegenOperation.returnType = "Single<${codegenOperation.returnType}>"
codegenOperation.imports.add("io.reactivex.Single")
wrapResponseType(codegenOperation.imports, codegenOperation.returnType)
}
}

Expand Down Expand Up @@ -425,4 +422,22 @@ class KotlinGenerator : SharedCodegen() {
// Override the swagger version with the one provided from command line.
swagger.info.version = additionalProperties[SPEC_VERSION] as String
}

/**
* Wraps the return type of an operation with the proper type (e.g. Single, Observable, Future, etc.)
* Use this method to eventually add imports if needed in the [imports] param.
*/
protected open fun wrapResponseType(imports: MutableSet<String>, responsePrimitiveType: String): String {
imports.add("io.reactivex.Single")
return "Single<$responsePrimitiveType>"
}

/**
* Get the return type of operations with no ResponseType set (void, Unit, Completables).
* Use this method to eventually add imports if needed in the [imports] param.
*/
protected open fun getNoResponseType(imports: MutableSet<String>): String {
imports.add("io.reactivex.Completable")
return "Completable"
}
}
1 change: 1 addition & 0 deletions plugin/src/main/java/com/yelp/codegen/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ fun main(args: Array<String>) {
configurator.outputDir = parsed['o']

configurator.addAdditionalProperty(LANGUAGE, parsed['p'])

configurator.addAdditionalProperty(SPEC_VERSION, specVersion)
configurator.addAdditionalProperty(SERVICE_NAME, parsed['s'])
configurator.addAdditionalProperty(GROUP_ID, parsed['g'])
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
com.yelp.codegen.KotlinGenerator
com.yelp.codegen.KotlinCoroutineGenerator
23 changes: 23 additions & 0 deletions plugin/src/main/resources/kotlin-coroutines/data_class.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* {{{description}}}
{{#vars}}
* @property {{{name}}} {{description}}
{{/vars}}
*/
{{#hasVars}}data {{/hasVars}}class {{classname}} {{#hasVars}}(
{{#requiredVars}}
{{>data_class_req_var}}{{^-last}},
{{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}},
{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^-last}},
{{/-last}}{{/optionalVars}}
){{#hasEnums}} {
{{#vars}}
{{#isEnum}}
/**
* {{{description}}}
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}
*/
enum class {{enumName}}(val value: {{complexType}}){ {{#allowableValues}}{{#enumVars}}
@Json(name = {{{value}}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}}{{/-last}}{{/enumVars}}{{/allowableValues}}
}
{{/isEnum}}{{/vars}}}{{/hasEnums}}{{/hasVars}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@Json(name = "{{{baseName}}}") @field:Json(name = "{{{baseName}}}") {{#vendorExtensions.x-nullable}}@XNullable {{/vendorExtensions.x-nullable}}var {{{name}}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}} = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@Json(name = "{{{baseName}}}") @field:Json(name = "{{{baseName}}}") var {{{name}}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* {{{description}}}
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
*/
enum class {{classname}}(val value: {{dataType}}){
{{#allowableValues}}{{#enumVars}} @Json(name = {{{value}}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}
{{/enumVars}}{{/allowableValues}}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* NOTE: This class is auto generated by the Swagger Gradle Codegen for the following API: {{{appName}}}
*
* More info on this tool is available on https://github.com/Yelp/swagger-gradle-codegen
*/
11 changes: 11 additions & 0 deletions plugin/src/main/resources/kotlin-coroutines/model.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{{>file_header}}
package {{modelPackage}}

{{#imports}}import {{import}}
{{/imports}}

{{#models}}
{{#model}}
{{#isAlias}}{{>type_alias}}{{/isAlias}}{{^isAlias}}{{#isEnum}}{{>enum_class}}{{/isEnum}}{{^isEnum}}{{>data_class}}{{/isEnum}}{{/isAlias}}
{{/model}}
{{/models}}
54 changes: 54 additions & 0 deletions plugin/src/main/resources/kotlin-coroutines/retrofit2/api.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{{>file_header}}
package {{package}}

import okhttp3.RequestBody

{{#imports}}import {{import}}
{{/imports}}

{{#operations}}
@JvmSuppressWildcards
interface {{classname}} {
{{#operation}}
/**{{#summary}} {{{newline}}} * {{{summary}}}{{/summary}}{{#notes}}{{{newline}}} * {{{<br/>}}}{{{notes}}}{{/notes}}
* The endpoint is owned by {{{vendorExtensions.x-team-owners}}}{{^vendorExtensions.x-team-owners}}{{service_name}} service owner{{/vendorExtensions.x-team-owners}}
{{#allParams}}
* @param {{paramName}} {{description}}{{#required}} (required){{/required}}{{^required}} (optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/allParams}}
{{#externalDocs}}
* {{description}}
* @see [{{url}}"][{{summary}} Documentation]
{{/externalDocs}}
*/
{{#formParams}}
{{#-first}}
{{#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}}
)

@{{httpMethod}}("{{{path}}}"){{#vendorExtensions.x-unsafe-operation}}{{#isDeprecated}}
@Deprecated(message = "Deprecated and unsafe to use"){{/isDeprecated}}{{^isDeprecated}}
@Deprecated(message = "Unsafe to use"){{/isDeprecated}}
{{/vendorExtensions.x-unsafe-operation}}{{^vendorExtensions.x-unsafe-operation}}{{#isDeprecated}}
@Deprecated(message = "Deprecated"){{/isDeprecated}}
{{/vendorExtensions.x-unsafe-operation}}
suspend fun {{operationId}}({{^allParams}}){{/allParams}}
{{#allParams}}{{>retrofit2/queryParams}}{{>retrofit2/pathParams}}{{>retrofit2/headerParams}}{{>retrofit2/bodyParams}}{{>retrofit2/formParams}}{{#hasMore}},
{{/hasMore}}{{^hasMore}}
){{/hasMore}}{{/allParams}}: {{{returnType}}}

{{/operation}}
}
{{/operations}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{#isBodyParam}}@retrofit2.http.Body {{paramName}}: {{{dataType}}}{{/isBodyParam}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{#isFormParam}}{{#notFile}}{{#isMultipart}}@retrofit2.http.Part{{/isMultipart}}{{^isMultipart}}@retrofit2.http.Field{{/isMultipart}}("{{baseName}}") {{paramName}}: {{{dataType}}}{{/notFile}}{{#isFile}}{{#isMultipart}}@retrofit2.http.Part{{/isMultipart}}{{^isMultipart}}@retrofit2.http.Field{{/isMultipart}}("{{baseName}}\"; filename=\"{{baseName}}") {{paramName}}: RequestBody{{^required}}?{{/required}} {{/isFile}}{{/isFormParam}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{#isHeaderParam}}@retrofit2.http.Header("{{baseName}}") {{paramName}}: {{{dataType}}}{{/isHeaderParam}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{#isPathParam}}@retrofit2.http.Path("{{baseName}}") {{paramName}}: {{{dataType}}}{{/isPathParam}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{#isQueryParam}}@retrofit2.http.Query("{{baseName}}") {{#collectionFormat}}{{^isCollectionFormatMulti}}@{{{collectionFormat.toUpperCase}}} {{/isCollectionFormatMulti}}{{/collectionFormat}}{{paramName}}: {{{dataType}}}{{/isQueryParam}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package {{packageName}}.tools

import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type

internal class CollectionFormatConverterFactory : Converter.Factory() {
override fun stringConverter(type: Type, annotations: Array<out Annotation>, retrofit: Retrofit): Converter<*, String>? {
val rawType = getRawType(type)
if (rawType == String::class.java || rawType == List::class.java)
annotations.forEach {
when (it) {
is CSV -> return CollectionFormatConverter(",")
is SSV -> return CollectionFormatConverter(" ")
is TSV -> return CollectionFormatConverter("\t")
is PIPES -> return CollectionFormatConverter("|")
}
}
return null
}

private class CollectionFormatConverter(private val separator: String) : Converter<Any, String> {
override fun convert(value: Any): String {
when (value) {
is String -> return value
is List<*> -> return value.joinToString(separator)
}
throw RuntimeException("Unsupported type")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package {{packageName}}.tools

@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class CSV


@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class SSV


@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class TSV


@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class PIPES
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package {{packageName}}.tools

import com.squareup.moshi.Json
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type

internal class EnumToValueConverterFactory : Converter.Factory() {
private val enumConverter = EnumToValueConverter()
override fun stringConverter(type: Type, annotations: Array<out Annotation>, retrofit: Retrofit): Converter<*, String>? {
return if (type is Class<*> && type.isEnum) {
enumConverter
} else {
null
}
}

internal class EnumToValueConverter : Converter<Any, String> {
override fun convert(enum: Any): String? {
val enumName = (enum as Enum<*>).name
val jsonAnnotation : Json? = enum.javaClass.getField(enumName).getAnnotation(Json::class.java)
// Checking if the Enum is annotated with @Json to get the name.
// If not, fallback to enum default (.toString())
return if (jsonAnnotation != null) {
jsonAnnotation.name
} else {
enum.toString()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package {{packageName}}.tools

import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Converter
import retrofit2.converter.moshi.MoshiConverterFactory


object GeneratedCodeConverters {
private val moshi = Moshi.Builder()
.add(XNullableAdapterFactory())
.add(KotlinJsonAdapterFactory())
.add(TypesAdapterFactory())
.build()
/**
* Creates everything needed for retrofit to make it work with the client lib, including a
* [Moshi] instance. If you want to use your own instance of moshi, use
* converterFactory(moshi) instead, and add [XNullableAdapterFactory], [KotlinJsonAdapterFactory] and
* [TypesAdapterFactory] to your moshi builder (in a similar way how we are instantiating the `moshi` field here).
*/
@JvmStatic
fun converterFactory(): Converter.Factory {
return WrapperConverterFactory(
CollectionFormatConverterFactory(),
EnumToValueConverterFactory(),
MoshiConverterFactory.create(moshi)
)
}

@JvmStatic
fun converterFactory(moshi: Moshi): Converter.Factory {
return WrapperConverterFactory(
CollectionFormatConverterFactory(),
EnumToValueConverterFactory(),
MoshiConverterFactory.create(moshi)
)
}
}
Loading

0 comments on commit 7ec9866

Please sign in to comment.