diff --git a/gradle-plugin/plugin/src/main/java/com/yelp/codegen/KotlinGenerator.kt b/gradle-plugin/plugin/src/main/java/com/yelp/codegen/KotlinGenerator.kt index 0e3e7901..bbd5fd4b 100644 --- a/gradle-plugin/plugin/src/main/java/com/yelp/codegen/KotlinGenerator.kt +++ b/gradle-plugin/plugin/src/main/java/com/yelp/codegen/KotlinGenerator.kt @@ -202,8 +202,7 @@ open class KotlinGenerator : SharedCodegen() { return codegenModel } - @VisibleForTesting - internal fun addRequiredImports(codegenModel: CodegenModel) { + override fun addRequiredImports(codegenModel: CodegenModel) { // If there are any vars, we will mark them with the @Json annotation so we have to make sure to import it if (codegenModel.allVars.isNotEmpty() || codegenModel.isEnum) { codegenModel.imports.add("com.squareup.moshi.Json") @@ -224,35 +223,6 @@ open class KotlinGenerator : SharedCodegen() { } } - override fun postProcessModelProperty(model: CodegenModel, property: CodegenProperty) { - super.postProcessModelProperty(model, property) - - if (property.isEnum) { - property.datatypeWithEnum = postProcessDataTypeWithEnum(model.classname, property) - } - } - - /** - * When handling inner enums, we want to prefix their class name, when using them, with their containing class, - * to avoid name conflicts. - */ - private fun postProcessDataTypeWithEnum(modelClassName: String, codegenProperty: CodegenProperty): String { - val name = "$modelClassName.${codegenProperty.enumName}" - - val baseType = if (codegenProperty.isContainer) { - val type = checkNotNull(typeMapping[codegenProperty.containerType]) - "$type<$name>" - } else { - name - } - - return if (codegenProperty.isNullable()) { - nullableTypeWrapper(baseType) - } else { - baseType - } - } - /** * Returns the swagger type for the property * diff --git a/gradle-plugin/plugin/src/main/java/com/yelp/codegen/SharedCodegen.kt b/gradle-plugin/plugin/src/main/java/com/yelp/codegen/SharedCodegen.kt index d717ad23..3cc6a116 100644 --- a/gradle-plugin/plugin/src/main/java/com/yelp/codegen/SharedCodegen.kt +++ b/gradle-plugin/plugin/src/main/java/com/yelp/codegen/SharedCodegen.kt @@ -1,9 +1,12 @@ package com.yelp.codegen +import com.google.common.annotations.VisibleForTesting +import com.yelp.codegen.utils.CodegenModelVar import com.yelp.codegen.utils.safeSuffix import io.swagger.codegen.CodegenConfig import io.swagger.codegen.CodegenModel import io.swagger.codegen.CodegenOperation +import io.swagger.codegen.CodegenParameter import io.swagger.codegen.CodegenProperty import io.swagger.codegen.CodegenType import io.swagger.codegen.DefaultCodegen @@ -80,8 +83,8 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { /** * Returns the /main/resources directory to access the .mustache files */ - protected val resourcesDirectory: File - get() = File(this.javaClass.classLoader.getResource(templateDir).path.safeSuffix(File.separator)) + protected val resourcesDirectory: File? + get() = javaClass.classLoader.getResource(templateDir)?.path?.safeSuffix(File.separator)?.let { File(it) } override fun processOpts() { super.processOpts() @@ -259,6 +262,13 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { } handleXNullable(codegenModel) + + // Update all enum properties datatypeWithEnum to use "BaseClass.InnerEnumClass" to reduce ambiguity + CodegenModelVar.forEachVarAttribute(codegenModel) { _, properties -> + properties.filter { it.isEnum } + .onEach { it.datatypeWithEnum = postProcessDataTypeWithEnum(codegenModel, it) } + } + return codegenModel } @@ -283,6 +293,37 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { } } + override fun postProcessAllModels(objs: Map): Map { + val postProcessedModels = super.postProcessAllModels(objs) + + // postProcessedModel does contain a map like Map + // ContentOfTheModelAsPassedToMustache would look like the following + // { + // : { + // + // "models": [ + // { + // "importPath": , + // "model": + // } + // ] + // } + // } + postProcessedModels.values + .asSequence() + .filterIsInstance>() + .mapNotNull { it["models"] } + .filterIsInstance>>() + .mapNotNull { it[0]["model"] } + .filterIsInstance() + .forEach { codegenModel -> + // Ensure that after all the processing done on the CodegenModel.*Vars, hasMore does still make sense + CodegenModelVar.forEachVarAttribute(codegenModel) { _, properties -> properties.fixHasMoreProperty() } + } + + return postProcessedModels + } + /** * Private method to investigate all the properties of a models, filter only the [RefProperty] and eventually * propagate the `x-nullable` vendor extension. @@ -368,7 +409,7 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { } // If we removed the last parameter of the Operation, we should update the `hasMore` flag. - codegenOperation.allParams.lastOrNull()?.hasMore = false + codegenOperation.allParams.fixHasMoreParameter() } /** @@ -412,7 +453,7 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { * or `items` at the top level (Arrays). * Their returned type would be a `Map` or `List`, where `Any?` will be the aliased type. * - * The method will call [KotlinAndroidGenerator.resolvePropertyType] that will perform a check if the model + * The method will call [KotlinGenerator.resolvePropertyType] that will perform a check if the model * is aliasing to a 'x-nullable' annotated model and compute the proper type (adding a `?` if needed). * * ``` @@ -562,6 +603,13 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { */ protected abstract fun nullableTypeWrapper(baseType: String): String + /** + * Hook that allows to add the needed imports for a given [CodegenModel] + * This is needed as we might be modifying models in [postProcessAllModels] + */ + @VisibleForTesting + internal abstract fun addRequiredImports(codegenModel: CodegenModel) + private fun defaultListType() = typeMapping["list"] ?: "" private fun defaultMapType() = typeMapping["map"] ?: "" @@ -584,4 +632,53 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { * Nullable type are either not required or x-nullable annotated properties. */ internal fun CodegenProperty.isNullable() = !this.required || this.vendorExtensions[X_NULLABLE] == true + + override fun postProcessModelProperty(model: CodegenModel, property: CodegenProperty) { + super.postProcessModelProperty(model, property) + + if (property.isEnum) { + property.datatypeWithEnum = this.postProcessDataTypeWithEnum(model, property) + } + } + + /** + * When handling inner enums, we want to prefix their class name, when using them, with their containing class, + * to avoid name conflicts. + */ + private fun postProcessDataTypeWithEnum(codegenModel: CodegenModel, codegenProperty: CodegenProperty): String { + val name = "${codegenModel.classname}.${codegenProperty.enumName}" + + val baseType = if (codegenProperty.isContainer) { + val type = checkNotNull(typeMapping[codegenProperty.containerType]) + "$type<$name>" + } else { + name + } + + return if (codegenProperty.isNullable()) { + nullableTypeWrapper(baseType) + } else { + baseType + } + } +} + +/** + * Small helper to ensurer that the hasMore attribute is properly + * defined within a list of [CodegenProperty]s + */ +internal fun List.fixHasMoreProperty() { + this.forEachIndexed { index, item -> + item.hasMore = index != (this.size - 1) + } +} + +/** + * Small helper to ensurer that the hasMore attribute is properly + * defined within a list of [CodegenParameter]s + */ +internal fun List.fixHasMoreParameter() { + this.forEachIndexed { index, item -> + item.hasMore = index != (this.size - 1) + } } diff --git a/gradle-plugin/plugin/src/main/java/com/yelp/codegen/utils/CodegenModelVar.kt b/gradle-plugin/plugin/src/main/java/com/yelp/codegen/utils/CodegenModelVar.kt new file mode 100644 index 00000000..9a50796b --- /dev/null +++ b/gradle-plugin/plugin/src/main/java/com/yelp/codegen/utils/CodegenModelVar.kt @@ -0,0 +1,40 @@ +package com.yelp.codegen.utils + +import io.swagger.codegen.CodegenModel +import io.swagger.codegen.CodegenProperty + +internal enum class CodegenModelVar { + ALL_VARS, + OPTIONAL_VARS, + PARENT_VARS, + READ_ONLY_VARS, + READ_WRITE_VARS, + REQUIRED_VARS, + VARS; + + companion object { + /** + * Allow the execution of an action on all the var attributes + */ + fun forEachVarAttribute( + codegenModel: CodegenModel, + action: (CodegenModelVar, MutableList) -> Unit + ) { + values().forEach { + action(it, it.value(codegenModel)) + } + } + } + + internal fun value(codegenModel: CodegenModel): MutableList { + return when (this) { + ALL_VARS -> codegenModel.allVars + OPTIONAL_VARS -> codegenModel.optionalVars + PARENT_VARS -> codegenModel.parentVars + READ_ONLY_VARS -> codegenModel.readOnlyVars + READ_WRITE_VARS -> codegenModel.readWriteVars + REQUIRED_VARS -> codegenModel.requiredVars + VARS -> codegenModel.vars + } + } +}