Skip to content

Commit

Permalink
Merge pull request OpenAPITools#1 from Thecrazyskull/master
Browse files Browse the repository at this point in the history
Upgrade to kotlin 1.4 & bug fixes for kotlin MPP
  • Loading branch information
dzolnai authored Sep 2, 2020
2 parents 20eb4d0 + d797b6e commit d9e8717
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public AbstractKotlinCodegen() {
"false",
"for",
"fun",
"header",
"if",
"in",
"interface",
Expand Down Expand Up @@ -756,6 +757,59 @@ private String titleCase(final String input) {
return input.substring(0, 1).toUpperCase(Locale.ROOT) + input.substring(1);
}

private CodegenModel reconcileProperties(CodegenModel codegenModel,
CodegenModel parentCodegenModel) {
// To support inheritance in this generator, we will analyze
// the parent and child models, look for properties that match, and mark them
// as inherited while copying the data types over to the child.
// Because the only way we have inheritance is with discriminator interface, in
// which it is mandatory to implement and override all parent properties.

// Get the properties for the parent and child models
final List<CodegenProperty> parentModelCodegenProperties = parentCodegenModel.vars;
List<CodegenProperty> codegenProperties = codegenModel.vars;
codegenModel.allVars = new ArrayList<CodegenProperty>(codegenProperties);
codegenModel.parentVars = parentCodegenModel.allVars;

// Iterate over all of the parent model properties
for (CodegenProperty parentModelCodegenProperty : parentModelCodegenProperties) {
// Now that we have found a prop in the parent class,
// and search the child class for the same prop.
Iterator<CodegenProperty> iterator = codegenProperties.iterator();
while (iterator.hasNext()) {
CodegenProperty codegenProperty = iterator.next();
if (codegenProperty.baseName.equals(parentModelCodegenProperty.baseName)
&& parentModelCodegenProperty.isEnum) {
// We found a property in the child class that is
// a duplicate of the one in the parent,
// so mark it as inherited & copy the data type.
codegenProperty.isInherited = true;
codegenProperty.isEnum = parentModelCodegenProperty.isEnum;
codegenProperty.baseType = parentModelCodegenProperty.baseType;
codegenProperty.dataType = parentModelCodegenProperty.dataType;
codegenProperty.datatypeWithEnum = parentModelCodegenProperty.datatypeWithEnum;
}
}
}

// We add all parent properties to the child, excluding the ones already defined
final List<CodegenProperty> missingParentProperties = new ArrayList<CodegenProperty>();
final Set<String> allVarsNameSet = codegenModel.allVars.stream()
.map(CodegenProperty::getBaseName).collect(Collectors.toSet());
for (CodegenProperty property : codegenModel.parentVars) {
if (!allVarsNameSet.contains(property.getBaseName())) {
property.isInherited = true;
missingParentProperties.add(property);
}
}

// Insert the inherited parent properties first
codegenModel.allVars.addAll(0, missingParentProperties);
codegenModel.vars = codegenProperties;

return codegenModel;
}

@Override
protected boolean isReservedWord(String word) {
// We want case-sensitive escaping, to avoid unnecessary backtick-escaping.
Expand All @@ -779,6 +833,23 @@ protected boolean needToImport(String type) {
public CodegenModel fromModel(String name, Schema schema) {
CodegenModel m = super.fromModel(name, schema);
m.optionalVars = m.optionalVars.stream().distinct().collect(Collectors.toList());

Map<String, Schema> allDefinitions = ModelUtils.getSchemas(this.openAPI);
if (allDefinitions != null) {
String parentSchema = m.parentSchema;

// multilevel inheritance: reconcile properties of all the parents
while (parentSchema != null) {
final Schema parentModel = allDefinitions.get(parentSchema);
final CodegenModel parentCodegenModel = super.fromModel(m.parent,
parentModel);
m = reconcileProperties(m, parentCodegenModel);

// get the next parent
parentSchema = parentCodegenModel.parentSchema;
}
}

// Update allVars/requiredVars/optionalVars with isInherited
// Each of these lists contains elements that are similar, but they are all cloned
// via CodegenModel.removeAllDuplicatedProperty and therefore need to be updated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import kotlinx.android.parcel.Parcelize
{{/multiplatform}}
{{#multiplatform}}
import kotlinx.serialization.*
import kotlinx.serialization.internal.CommonEnumSerializer
{{#discriminator}}
import {{packageName}}.util.serializers.*
{{/discriminator}}
{{/multiplatform}}
{{#serializableModel}}
import java.io.Serializable
Expand All @@ -38,7 +40,12 @@ import java.io.Serializable
{{#parcelizeModels}}
@Parcelize
{{/parcelizeModels}}
{{#multiplatform}}@Serializable{{/multiplatform}}{{#moshi}}{{#moshiCodeGen}}@JsonClass(generateAdapter = true){{/moshiCodeGen}}{{/moshi}}{{#jackson}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{/jackson}}
{{#multiplatform}}
{{#discriminator}}
@ExperimentalSerializationApi
{{/discriminator}}
{{/multiplatform}}
{{#multiplatform}}@Serializable{{#discriminator}}(with = Custom{{classname}}Serializer::class){{/discriminator}}{{/multiplatform}}{{#moshi}}{{#moshiCodeGen}}@JsonClass(generateAdapter = true){{/moshiCodeGen}}{{/moshi}}{{#jackson}}{{#discriminator}}{{>typeInfoAnnotation}}{{/discriminator}}{{/jackson}}
{{#isDeprecated}}
@Deprecated(message = "This schema is deprecated.")
{{/isDeprecated}}
Expand All @@ -63,7 +70,7 @@ import java.io.Serializable
* {{{description}}}
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
*/
{{#multiplatform}}@Serializable(with = {{nameInCamelCase}}.Serializer::class){{/multiplatform}}
{{#multiplatform}}@Serializable{{/multiplatform}}
{{#nonPublicApi}}internal {{/nonPublicApi}}enum class {{{nameInCamelCase}}}(val value: {{^isContainer}}{{dataType}}{{/isContainer}}{{#isContainer}}kotlin.String{{/isContainer}}){
{{#allowableValues}}
{{#enumVars}}
Expand All @@ -79,14 +86,10 @@ import java.io.Serializable
{{/jackson}}
{{/multiplatform}}
{{#multiplatform}}
{{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
@SerialName(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
{{/multiplatform}}
{{/enumVars}}
{{/allowableValues}}
{{#multiplatform}}

{{#nonPublicApi}}internal {{/nonPublicApi}}object Serializer : CommonEnumSerializer<{{nameInCamelCase}}>("{{nameInCamelCase}}", values(), values().map { it.value.toString() }.toTypedArray())
{{/multiplatform}}
}
{{/isEnum}}
{{/vars}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
{{#deprecated}}
@Deprecated(message = "This property is deprecated.")
{{/deprecated}}
{{#multiplatform}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") {{/multiplatform}}{{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isEnum}}{{#isListContainer}}{{#isList}}kotlin.collections.List{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{classname}}.{{{nameInCamelCase}}}>{{/isListContainer}}{{^isListContainer}}{{classname}}.{{{nameInCamelCase}}}{{/isListContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}
{{#multiplatform}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") {{/multiplatform}}{{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isEnum}}{{#isListContainer}}{{#isList}}kotlin.collections.List{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{classname}}.{{{nameInCamelCase}}}>{{/isListContainer}}{{^isListContainer}}{{#isInherited}}{{{parent}}}{{/isInherited}}{{^isInherited}}{{classname}}{{/isInherited}}.{{{nameInCamelCase}}}{{/isListContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
{{#deprecated}}
@Deprecated(message = "This property is deprecated.")
{{/deprecated}}
{{#multiplatform}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") @Required {{/multiplatform}}{{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isEnum}}{{#isListContainer}}{{#isList}}kotlin.collections.List{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{classname}}.{{{nameInCamelCase}}}>{{/isListContainer}}{{^isListContainer}}{{classname}}.{{{nameInCamelCase}}}{{/isListContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}}?{{/isNullable}}
{{#multiplatform}}@SerialName(value = "{{{vendorExtensions.x-base-name-literal}}}") @Required {{/multiplatform}}{{#isInherited}}override {{/isInherited}}{{>modelMutable}} {{{name}}}: {{#isEnum}}{{#isListContainer}}{{#isList}}kotlin.collections.List{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{#isInherited}}{{{parent}}}{{/isInherited}}{{^isInherited}}{{classname}}{{/isInherited}}.{{{nameInCamelCase}}}>{{/isListContainer}}{{^isListContainer}}{{#isInherited}}{{{parent}}}{{/isInherited}}{{^isInherited}}{{classname}}{{/isInherited}}.{{{nameInCamelCase}}}{{/isListContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isNullable}}?{{/isNullable}}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import com.fasterxml.jackson.annotation.JsonProperty
{{/multiplatform}}
{{#multiplatform}}
import kotlinx.serialization.*
import kotlinx.serialization.internal.CommonEnumSerializer
{{/multiplatform}}

/**
Expand Down Expand Up @@ -54,8 +53,4 @@ import kotlinx.serialization.internal.CommonEnumSerializer
override fun toString(): String {
return value{{^isString}}.toString(){{/isString}}
}

{{#multiplatform}}
{{#nonPublicApi}}internal {{/nonPublicApi}}object Serializer : CommonEnumSerializer<{{classname}}>("{{classname}}", values(), values().map { it.value.toString() }.toTypedArray())
{{/multiplatform}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,27 @@ package {{apiPackage}}

import {{packageName}}.infrastructure.*
import io.ktor.client.request.forms.formData
import kotlinx.serialization.UnstableDefault
import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.features.json.serializer.KotlinxSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import io.ktor.http.ParametersBuilder
import kotlinx.serialization.*
import kotlinx.serialization.internal.StringDescriptor

{{#operations}}
{{#nonPublicApi}}internal {{/nonPublicApi}}class {{classname}} @UseExperimental(UnstableDefault::class) constructor(
{{#nonPublicApi}}internal {{/nonPublicApi}}class {{classname}} constructor(
baseUrl: kotlin.String = "{{{basePath}}}",
httpClientEngine: HttpClientEngine? = null,
serializer: KotlinxSerializer
) : ApiClient(baseUrl, httpClientEngine, serializer) {
@UseExperimental(UnstableDefault::class)
constructor(
baseUrl: kotlin.String = "{{{basePath}}}",
httpClientEngine: HttpClientEngine? = null,
jsonConfiguration: JsonConfiguration = JsonConfiguration.Default
) : this(baseUrl, httpClientEngine, KotlinxSerializer(Json(jsonConfiguration)))
json: Json = Json {
ignoreUnknownKeys = true
}
) : this(baseUrl, httpClientEngine, KotlinxSerializer(json))

{{#operation}}
/**
Expand Down Expand Up @@ -101,25 +100,6 @@ import kotlinx.serialization.internal.StringDescriptor
{{#isMapContainer}}
{{>serial_wrapper_response_map}}
{{/isMapContainer}}

{{/operation}}

{{#nonPublicApi}}internal {{/nonPublicApi}}companion object {
internal fun setMappers(serializer: KotlinxSerializer) {
{{#operation}}
{{#hasBodyParam}}
{{#bodyParam}}
{{#isListContainer}}serializer.setMapper({{operationIdCamelCase}}Request::class, {{operationIdCamelCase}}Request.serializer()){{/isListContainer}}{{#isMapContainer}}serializer.setMapper({{operationIdCamelCase}}Request::class, {{operationIdCamelCase}}Request.serializer()){{/isMapContainer}}
{{/bodyParam}}
{{/hasBodyParam}}
{{#isListContainer}}
serializer.setMapper({{operationIdCamelCase}}Response::class, {{operationIdCamelCase}}Response.serializer())
{{/isListContainer}}
{{#isMapContainer}}
serializer.setMapper({{operationIdCamelCase}}Response::class, {{operationIdCamelCase}}Response.serializer())
{{/isMapContainer}}
{{/operation}}
}
}
}
{{/operations}}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@ import io.ktor.client.request.forms.FormDataContent
import io.ktor.client.request.forms.MultiPartFormDataContent
import io.ktor.client.request.header
import io.ktor.client.request.parameter
import io.ktor.client.response.HttpResponse
import io.ktor.client.request.request
import io.ktor.client.statement.HttpResponse
import io.ktor.client.utils.EmptyContent
import io.ktor.http.*
import io.ktor.http.content.OutgoingContent
import io.ktor.http.content.PartData
import kotlinx.serialization.UnstableDefault
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration

import {{apiPackage}}.*
import {{modelPackage}}.*
Expand All @@ -30,15 +29,16 @@ import {{packageName}}.auth.*
httpClientEngine: HttpClientEngine?,
serializer: KotlinxSerializer) {
@UseExperimental(UnstableDefault::class)
constructor(
baseUrl: String,
httpClientEngine: HttpClientEngine?,
jsonConfiguration: JsonConfiguration) :
this(baseUrl, httpClientEngine, KotlinxSerializer(Json(jsonConfiguration)))
json: Json = Json {
ignoreUnknownKeys = true
}
) : this(baseUrl, httpClientEngine, KotlinxSerializer(json))

private val serializer: JsonSerializer by lazy {
serializer.apply { setMappers(this) }.ignoreOutgoingContent()
serializer.ignoreOutgoingContent()
}

private val client: HttpClient by lazy {
Expand All @@ -61,15 +61,6 @@ import {{packageName}}.auth.*

{{#nonPublicApi}}internal {{/nonPublicApi}}companion object {
protected val UNSAFE_HEADERS = listOf(HttpHeaders.ContentType)
private fun setMappers(serializer: KotlinxSerializer) {
{{#apiInfo}}{{#apis}}
{{classname}}.setMappers(serializer)
{{/apis}}{{/apiInfo}}
{{#models}}
{{#model}}{{^isAlias}}serializer.setMapper({{modelPackage}}.{{classname}}::class, {{modelPackage}}.{{classname}}.{{#isEnum}}Serializer{{/isEnum}}{{^isEnum}}serializer(){{/isEnum}}){{/isAlias}}{{/model}}
{{/models}}
}
}

/**
Expand Down Expand Up @@ -159,7 +150,7 @@ import {{packageName}}.auth.*
requestConfig.updateForAuth(authNames)
val headers = requestConfig.headers
return client.call {
return client.request {
this.url {
this.takeFrom(URLBuilder(baseUrl))
appendPath(requestConfig.path.trimStart('/').split('/'))
Expand All @@ -174,7 +165,7 @@ import {{packageName}}.auth.*
if (requestConfig.method in listOf(RequestMethod.PUT, RequestMethod.POST, RequestMethod.PATCH))
this.body = body

}.response
}
}

private fun RequestConfig.updateForAuth(authNames: kotlin.collections.List<String>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package {{packageName}}.infrastructure

import kotlinx.serialization.*
import kotlinx.serialization.internal.StringDescriptor
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

@Serializable
class Base64ByteArray(val value: ByteArray) {
@ExperimentalSerializationApi
@Serializer(Base64ByteArray::class)
companion object : KSerializer<Base64ByteArray> {
override val descriptor = StringDescriptor.withName("Base64ByteArray")
override val descriptor = PrimitiveSerialDescriptor("Base64ByteArray", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, obj: Base64ByteArray) = encoder.encodeString(obj.value.encodeBase64())
override fun deserialize(decoder: Decoder) = Base64ByteArray(decoder.decodeString().decodeBase64Bytes())
}
Expand All @@ -26,4 +30,4 @@ class Base64ByteArray(val value: ByteArray) {
override fun toString(): String {
return "Base64ByteArray(${hex(value)})"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package {{packageName}}.infrastructure

import kotlinx.io.core.*
import io.ktor.utils.io.core.*
import kotlin.experimental.and

private val digits = "0123456789abcdef".toCharArray()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import io.ktor.client.call.typeInfo
import io.ktor.http.Headers
import io.ktor.http.isSuccess

{{#nonPublicApi}}internal {{/nonPublicApi}}open class HttpResponse<T : Any>(val response: io.ktor.client.response.HttpResponse, val provider: BodyProvider<T>) {
{{#nonPublicApi}}internal {{/nonPublicApi}}open class HttpResponse<T : Any>(val response: io.ktor.client.statement.HttpResponse, val provider: BodyProvider<T>) {
val status: Int = response.status.value
val success: Boolean = response.status.isSuccess()
val headers: Map<String, List<String>> = response.headers.mapEntries()
Expand All @@ -22,29 +22,29 @@ import io.ktor.http.isSuccess
}

{{#nonPublicApi}}internal {{/nonPublicApi}}interface BodyProvider<T : Any> {
suspend fun body(response: io.ktor.client.response.HttpResponse): T
suspend fun <V : Any> typedBody(response: io.ktor.client.response.HttpResponse, type: TypeInfo): V
suspend fun body(response: io.ktor.client.statement.HttpResponse): T
suspend fun <V : Any> typedBody(response: io.ktor.client.statement.HttpResponse, type: TypeInfo): V
}

{{#nonPublicApi}}internal {{/nonPublicApi}}class TypedBodyProvider<T : Any>(private val type: TypeInfo) : BodyProvider<T> {
@Suppress("UNCHECKED_CAST")
override suspend fun body(response: io.ktor.client.response.HttpResponse): T =
override suspend fun body(response: io.ktor.client.statement.HttpResponse): T =
response.call.receive(type) as T
@Suppress("UNCHECKED_CAST")
override suspend fun <V : Any> typedBody(response: io.ktor.client.response.HttpResponse, type: TypeInfo): V =
override suspend fun <V : Any> typedBody(response: io.ktor.client.statement.HttpResponse, type: TypeInfo): V =
response.call.receive(type) as V
}

{{#nonPublicApi}}internal {{/nonPublicApi}}class MappedBodyProvider<S : Any, T : Any>(private val provider: BodyProvider<S>, private val block: S.() -> T) : BodyProvider<T> {
override suspend fun body(response: io.ktor.client.response.HttpResponse): T =
override suspend fun body(response: io.ktor.client.statement.HttpResponse): T =
block(provider.body(response))
override suspend fun <V : Any> typedBody(response: io.ktor.client.response.HttpResponse, type: TypeInfo): V =
override suspend fun <V : Any> typedBody(response: io.ktor.client.statement.HttpResponse, type: TypeInfo): V =
provider.typedBody(response, type)
}

{{#nonPublicApi}}internal {{/nonPublicApi}}inline fun <reified T : Any> io.ktor.client.response.HttpResponse.wrap(): HttpResponse<T> =
{{#nonPublicApi}}internal {{/nonPublicApi}}inline fun <reified T : Any> io.ktor.client.statement.HttpResponse.wrap(): HttpResponse<T> =
HttpResponse(this, TypedBodyProvider(typeInfo<T>()))

{{#nonPublicApi}}internal {{/nonPublicApi}}fun <T : Any, V : Any> HttpResponse<T>.map(block: T.() -> V): HttpResponse<V> =
Expand Down
Loading

0 comments on commit d9e8717

Please sign in to comment.