From 2ffc7d620789a0eca72083e68e8ea44cac1918d2 Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Sun, 26 Jan 2020 18:05:42 +0100 Subject: [PATCH 01/14] Move addRequiredImports into SharedCodegen --- .../src/main/java/com/yelp/codegen/KotlinGenerator.kt | 2 +- .../plugin/src/main/java/com/yelp/codegen/SharedCodegen.kt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) 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..0850c031 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 @@ -203,7 +203,7 @@ open class KotlinGenerator : SharedCodegen() { } @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") 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..c77c19d5 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 @@ -562,6 +562,12 @@ 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] + */ + internal abstract fun addRequiredImports(codegenModel: CodegenModel) + private fun defaultListType() = typeMapping["list"] ?: "" private fun defaultMapType() = typeMapping["map"] ?: "" From 398dc23b45506b5a9109b8cc55ed2d7d9b1c645c Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Sat, 25 Jan 2020 19:39:38 +0100 Subject: [PATCH 02/14] Define helper to iterate over all the var attributes of a CodegenModel instance --- .../com/yelp/codegen/utils/CodegenModelVar.kt | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 gradle-plugin/plugin/src/main/java/com/yelp/codegen/utils/CodegenModelVar.kt 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..96154d74 --- /dev/null +++ b/gradle-plugin/plugin/src/main/java/com/yelp/codegen/utils/CodegenModelVar.kt @@ -0,0 +1,38 @@ +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 + } + } +} From 7a2369f9f8966ec4afc90a931e548b88872fad82 Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Sun, 26 Jan 2020 17:15:09 +0100 Subject: [PATCH 03/14] Move postProcessDataTypeWithEnum in SharedCodegen and apply it to all CodegenModel vars attributes --- .../java/com/yelp/codegen/KotlinGenerator.kt | 29 -------------- .../java/com/yelp/codegen/SharedCodegen.kt | 39 +++++++++++++++++++ 2 files changed, 39 insertions(+), 29 deletions(-) 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 0850c031..91d06d22 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 @@ -224,35 +224,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 c77c19d5..e7384bd4 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 @@ -259,6 +259,16 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { } handleXNullable(codegenModel) + + // Update all enum properties datatypeWithEnum to use "BaseClass.InnerEnumClass" to reduce ambiguity + CodegenModelVar.forEachVarAttribute(codegenModel) { _, properties -> + properties.forEach { + if (it.isEnum) { + it.datatypeWithEnum = this.postProcessDataTypeWithEnum(codegenModel, it) + } + } + } + return codegenModel } @@ -590,4 +600,33 @@ 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 + } + } } From 4a7a4274b497bf795c970f004b17aa4b401793bb Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Tue, 4 Feb 2020 21:36:53 +0100 Subject: [PATCH 04/14] Ensure correctness of hasMore attribute of CodegenParameter and CodegenProperty --- .../java/com/yelp/codegen/SharedCodegen.kt | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) 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 e7384bd4..a2eda750 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 @@ -4,6 +4,7 @@ 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 @@ -293,6 +294,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. @@ -378,7 +410,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() } /** @@ -630,3 +662,23 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { } } } + +/** + * 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) + } +} From bde67fba9cb059f4054cda0109dfce2c7fdea0d5 Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Tue, 4 Feb 2020 21:39:58 +0100 Subject: [PATCH 05/14] Ensure that non-null resources (because if Java) are actually checked before usage (to limit kotlin warnings) --- .../plugin/src/main/java/com/yelp/codegen/SharedCodegen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 a2eda750..b73e0d52 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 @@ -82,7 +82,7 @@ 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)) + get() = File(this.javaClass.classLoader.getResource(templateDir)!!.path.safeSuffix(File.separator)) override fun processOpts() { super.processOpts() @@ -454,7 +454,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). * * ``` From cdd09a38d2dce00a2b1f7e5c35b614584aae4c97 Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Wed, 28 Oct 2020 22:20:45 +0000 Subject: [PATCH 06/14] PR Feedbacks --- .../main/java/com/yelp/codegen/KotlinGenerator.kt | 1 - .../main/java/com/yelp/codegen/SharedCodegen.kt | 14 +++++++------- .../java/com/yelp/codegen/utils/CodegenModelVar.kt | 2 ++ 3 files changed, 9 insertions(+), 8 deletions(-) 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 91d06d22..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,7 +202,6 @@ open class KotlinGenerator : SharedCodegen() { return codegenModel } - @VisibleForTesting 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) { 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 b73e0d52..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,5 +1,7 @@ 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 @@ -81,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() @@ -263,11 +265,8 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { // Update all enum properties datatypeWithEnum to use "BaseClass.InnerEnumClass" to reduce ambiguity CodegenModelVar.forEachVarAttribute(codegenModel) { _, properties -> - properties.forEach { - if (it.isEnum) { - it.datatypeWithEnum = this.postProcessDataTypeWithEnum(codegenModel, it) - } - } + properties.filter { it.isEnum } + .onEach { it.datatypeWithEnum = postProcessDataTypeWithEnum(codegenModel, it) } } return codegenModel @@ -608,6 +607,7 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { * 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"] ?: "" 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 index 96154d74..9a50796b 100644 --- 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 @@ -1,3 +1,5 @@ +package com.yelp.codegen.utils + import io.swagger.codegen.CodegenModel import io.swagger.codegen.CodegenProperty From 5fca7d76f2f5e54722aca0ddfca99edc6d53936f Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Wed, 22 Jan 2020 11:05:59 +0100 Subject: [PATCH 07/14] Define and plug Polymorphic annotation PolymorphicAdapterFactory class --- .../java/com/yelp/codegen/KotlinGenerator.kt | 2 + .../tools/OptimisticHashCode.kt.mustache | 4 ++ .../PolymorphicAdapterFactory.kt.mustache | 40 +++++++++++++++++++ .../tools/OptimisticHashCode.kt | 4 ++ .../tools/PolymorphicAdapterFactory.kt | 40 +++++++++++++++++++ 5 files changed, 90 insertions(+) create mode 100644 gradle-plugin/plugin/src/main/resources/kotlin/tools/OptimisticHashCode.kt.mustache create mode 100644 gradle-plugin/plugin/src/main/resources/kotlin/tools/PolymorphicAdapterFactory.kt.mustache create mode 100644 samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/tools/OptimisticHashCode.kt create mode 100644 samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/tools/PolymorphicAdapterFactory.kt 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 bbd5fd4b..6405e9ac 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 @@ -125,6 +125,8 @@ open class KotlinGenerator : SharedCodegen() { "CollectionFormats.kt", "EnumToValueConverterFactory.kt", "GeneratedCodeConverters.kt", + "OptimisticHashCode.kt", + "PolymorphicAdapterFactory.kt", "TypesAdapters.kt", "WrapperConverterFactory.kt", "XNullable.kt", diff --git a/gradle-plugin/plugin/src/main/resources/kotlin/tools/OptimisticHashCode.kt.mustache b/gradle-plugin/plugin/src/main/resources/kotlin/tools/OptimisticHashCode.kt.mustache new file mode 100644 index 00000000..cbc1b382 --- /dev/null +++ b/gradle-plugin/plugin/src/main/resources/kotlin/tools/OptimisticHashCode.kt.mustache @@ -0,0 +1,4 @@ +package {{packageName}}.tools + +@Suppress("NOTHING_TO_INLINE") +internal inline fun T.optimisticHashCode(): Int = this?.hashCode() ?: 0 diff --git a/gradle-plugin/plugin/src/main/resources/kotlin/tools/PolymorphicAdapterFactory.kt.mustache b/gradle-plugin/plugin/src/main/resources/kotlin/tools/PolymorphicAdapterFactory.kt.mustache new file mode 100644 index 00000000..44b4201f --- /dev/null +++ b/gradle-plugin/plugin/src/main/resources/kotlin/tools/PolymorphicAdapterFactory.kt.mustache @@ -0,0 +1,40 @@ +package {{packageName}}.tools + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory +import java.lang.reflect.Type +import kotlin.reflect.KClass + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class Polymorphic( + val discriminatorField: String, + val discriminatedValues: Array, + val discriminatedClasses: Array> +) + +internal class PolymorphicAdapterFactory : JsonAdapter.Factory { + override fun create(type: Type, annotations: MutableSet, moshi: Moshi): JsonAdapter<*>? { + val rawType = Types.getRawType(type) + return rawType.getAnnotation(Polymorphic::class.java)?.let { annotation -> + @Suppress("UNCHECKED_CAST") + val polymorphicJsonAdapterFactory: PolymorphicJsonAdapterFactory = PolymorphicJsonAdapterFactory.of( + rawType, annotation.discriminatorField + ) as? PolymorphicJsonAdapterFactory ?: return@let null + + return annotation.discriminatedClasses.zip(annotation.discriminatedValues).fold( + initial = polymorphicJsonAdapterFactory.withFallbackJsonAdapter( + moshi.nextAdapter( + this@PolymorphicAdapterFactory, + type, + annotations + ) + ) + ) { jsonAdapterFactory, (klass, label) -> + jsonAdapterFactory.withSubtype(klass.java, label) + }.create(type, annotations, moshi) + } + } +} diff --git a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/tools/OptimisticHashCode.kt b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/tools/OptimisticHashCode.kt new file mode 100644 index 00000000..1a9a1a4c --- /dev/null +++ b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/tools/OptimisticHashCode.kt @@ -0,0 +1,4 @@ +package com.yelp.codegen.generatecodesamples.tools + +@Suppress("NOTHING_TO_INLINE") +internal inline fun T.optimisticHashCode(): Int = this?.hashCode() ?: 0 diff --git a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/tools/PolymorphicAdapterFactory.kt b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/tools/PolymorphicAdapterFactory.kt new file mode 100644 index 00000000..8aca0e20 --- /dev/null +++ b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/tools/PolymorphicAdapterFactory.kt @@ -0,0 +1,40 @@ +package com.yelp.codegen.generatecodesamples.tools + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types +import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory +import java.lang.reflect.Type +import kotlin.reflect.KClass + +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class Polymorphic( + val discriminatorField: String, + val discriminatedValues: Array, + val discriminatedClasses: Array> +) + +internal class PolymorphicAdapterFactory : JsonAdapter.Factory { + override fun create(type: Type, annotations: MutableSet, moshi: Moshi): JsonAdapter<*>? { + val rawType = Types.getRawType(type) + return rawType.getAnnotation(Polymorphic::class.java)?.let { annotation -> + @Suppress("UNCHECKED_CAST") + val polymorphicJsonAdapterFactory: PolymorphicJsonAdapterFactory = PolymorphicJsonAdapterFactory.of( + rawType, annotation.discriminatorField + ) as? PolymorphicJsonAdapterFactory ?: return@let null + + return annotation.discriminatedClasses.zip(annotation.discriminatedValues).fold( + initial = polymorphicJsonAdapterFactory.withFallbackJsonAdapter( + moshi.nextAdapter( + this@PolymorphicAdapterFactory, + type, + annotations + ) + ) + ) { jsonAdapterFactory, (klass, label) -> + jsonAdapterFactory.withSubtype(klass.java, label) + }.create(type, annotations, moshi) + } + } +} From fbb81701b21566d7227fb815b5f6eaf815ebe5f9 Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Fri, 24 Jan 2020 09:01:04 +0100 Subject: [PATCH 08/14] Update SharedCodegen to properly deal with inheritance The base codegen that we're using does not really keep all the properties in sync, which is not great, but other templates are already applying this type of fixes. Trying to create a PR would eventually not be beneficial since we're still based on swagger-codegen version 2 (while version 3 has been released) --- .../java/com/yelp/codegen/KotlinGenerator.kt | 8 +- .../java/com/yelp/codegen/SharedCodegen.kt | 77 +++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) 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 6405e9ac..f6874410 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 @@ -210,7 +210,7 @@ open class KotlinGenerator : SharedCodegen() { codegenModel.imports.add("com.squareup.moshi.Json") } - if (!codegenModel.isAlias) { + if (!codegenModel.isAlias || codegenModel.parent != null) { // If we are rendering a model (or enum) we are annotating it with @JsonClass, // so we need to make sure that we're importing it codegenModel.imports.add("com.squareup.moshi.JsonClass") @@ -223,6 +223,12 @@ open class KotlinGenerator : SharedCodegen() { break } } + + if (supportsInheritance && codegenModel.discriminator != null) { + // Import JsonClass annotation to enable Adapters generations via Kotlin Annotation Processor + codegenModel.imports.add("$toolsPackage.optimisticHashCode") + codegenModel.imports.add("$toolsPackage.Polymorphic") + } } /** 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 3cc6a116..64e296ca 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 @@ -269,6 +269,20 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { .onEach { it.datatypeWithEnum = postProcessDataTypeWithEnum(codegenModel, it) } } + // Fix presence of duplicated variables into CodegenModel instances. + // Apparently swagger-codegen does not really play well with such properties if + // inheritance is enable and models have `allOf` attribute. + CodegenModelVar.forEachVarAttribute(codegenModel) { _, properties -> + val seenProperties = mutableSetOf() + val propertiesIterator = properties.iterator() + while (propertiesIterator.hasNext()) { + val property = propertiesIterator.next() + if (!seenProperties.add(property)) { + propertiesIterator.remove() + } + } + } + return codegenModel } @@ -293,6 +307,31 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { } } + /** + * Codegen does use the same [CodegenProperty] in the child models, is the property is inherited + * + * This method allows the creation of clones of the [CodegenProperty] instances in case they are + * present due to inheritance. + * + * This additional computation is needed to allow further customisation of the child model + * property definitions without parent model property modifications. + * One example of use-case is the update of isInherited attribute of [CodegenProperty] + */ + private fun decoupleParentProperties(codegenModel: CodegenModel) { + val nameToParentClone = codegenModel.parentVars.map { + it.name to it.clone() + }.toMap() + CodegenModelVar.forEachVarAttribute(codegenModel) { type, properties -> + if (type != CodegenModelVar.PARENT_VARS) { + properties.forEachIndexed { index, property -> + nameToParentClone[property.name]?.let { + properties[index] = it + } + } + } + } + } + override fun postProcessAllModels(objs: Map): Map { val postProcessedModels = super.postProcessAllModels(objs) @@ -319,6 +358,44 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { .forEach { codegenModel -> // Ensure that after all the processing done on the CodegenModel.*Vars, hasMore does still make sense CodegenModelVar.forEachVarAttribute(codegenModel) { _, properties -> properties.fixHasMoreProperty() } + + if (supportsInheritance && true == codegenModel.children?.isNotEmpty()) { + // Codegen does update the children attribute but does not keep hasChildren consistent + codegenModel.hasChildren = true + } + + if (supportsInheritance && codegenModel.parentModel != null) { + val parentPropertyNames = codegenModel.parentModel.allVars.map { it.name }.toSet() + + // Update parentVars (as codegen does not do it while updating the parents) + codegenModel.parentVars?.addAll(codegenModel.parentModel.allVars) + + decoupleParentProperties(codegenModel) + + // Update all the *Vars attributes to keep track of parent-inherited parameters + CodegenModelVar.forEachVarAttribute(codegenModel) { type, properties -> + if (type != CodegenModelVar.PARENT_VARS) { + properties.forEach { + it.isInherited = it.name in parentPropertyNames + } + } + } + // Objects with a single item in allOf are recognized as aliases, as this is usually done + // to override the content of metadata (ie. description). + // The tool does already help avoid a new type creation by creating a type alias + // In the case of Polymorphism this is actually a problem as the user would expect an + // instance of a different class, even if the content is the same of the parent class + if (codegenModel.isAlias) { + codegenModel.isAlias = false + codegenModel.hasVars = codegenModel.allVars.isNotEmpty() + addRequiredImports(codegenModel) + } + + // codegenModel.hasEnums will be set to true even if the current model does not specify + // an enum value but the parent does. + // In order to avoid to consider this model as a model with enums we're re-evaluating it. + codegenModel.hasEnums = codegenModel.vars.any { it.isEnum } + } } return postProcessedModels From ebd686ce85877ac377ec76b77fffd3844e5fce31 Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Fri, 24 Jan 2020 09:01:51 +0100 Subject: [PATCH 09/14] Enable inheritance for kotlin (and consequently kotlin-coroutines) generator --- .../plugin/src/main/java/com/yelp/codegen/KotlinGenerator.kt | 2 ++ 1 file changed, 2 insertions(+) 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 f6874410..a2f5d449 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 @@ -61,6 +61,8 @@ open class KotlinGenerator : SharedCodegen() { outputFolder = "generated-code${File.separator}android-kotlin-client" modelTemplateFiles["model.mustache"] = ".kt" apiTemplateFiles["retrofit2/api.mustache"] = ".kt" + + supportsInheritance = true } /* From a1598f90f58c93cbf71dedb5596dc818aa116be8 Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Fri, 24 Jan 2020 09:03:04 +0100 Subject: [PATCH 10/14] Update data-class template to allow inheritance when a parent module is defined --- .../plugin/src/main/resources/kotlin/data_class.mustache | 6 +++--- .../models/NestedAdditionalPropertiesCustomDescription.kt | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/gradle-plugin/plugin/src/main/resources/kotlin/data_class.mustache b/gradle-plugin/plugin/src/main/resources/kotlin/data_class.mustache index 8ef6fba5..d6fbbc20 100644 --- a/gradle-plugin/plugin/src/main/resources/kotlin/data_class.mustache +++ b/gradle-plugin/plugin/src/main/resources/kotlin/data_class.mustache @@ -1,8 +1,8 @@ /**{{#description}} * {{{description}}}{{/description}} -{{#vars}} +{{#allVars}} * @property {{{name}}}{{#description}} {{description}}{{/description}} -{{/vars}} +{{/allVars}} */ @JsonClass(generateAdapter = true) {{#hasVars}}data {{/hasVars}}class {{classname}}{{#hasVars}}( @@ -11,7 +11,7 @@ {{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}}, {{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^-last}}, {{/-last}}{{/optionalVars}} -){{#hasEnums}} { +){{#parent}} : {{parent}}({{#parentVars}}{{name}} = {{name}}{{#hasMore}}, {{/hasMore}}{{/parentVars}}){{/parent}}{{#hasEnums}} { {{#vars}} {{#isEnum}} /**{{#description}} diff --git a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/NestedAdditionalPropertiesCustomDescription.kt b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/NestedAdditionalPropertiesCustomDescription.kt index 9a05a438..eff4c51d 100644 --- a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/NestedAdditionalPropertiesCustomDescription.kt +++ b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/NestedAdditionalPropertiesCustomDescription.kt @@ -6,4 +6,9 @@ package com.yelp.codegen.generatecodesamples.models -typealias NestedAdditionalPropertiesCustomDescription = NestedAdditionalProperties +import com.squareup.moshi.JsonClass + +/** + */ +@JsonClass(generateAdapter = true) +class NestedAdditionalPropertiesCustomDescription From 0bdd04890674555dacbf0afae34112720ce770c2 Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Wed, 5 Feb 2020 17:47:27 +0100 Subject: [PATCH 11/14] Update data_class_*_var templates (properties templates) to deal with overridden properties (open and override modifiers) --- .../src/main/resources/kotlin/data_class_opt_var.mustache | 2 +- .../src/main/resources/kotlin/data_class_req_var.mustache | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle-plugin/plugin/src/main/resources/kotlin/data_class_opt_var.mustache b/gradle-plugin/plugin/src/main/resources/kotlin/data_class_opt_var.mustache index 2e2566e2..5f00f377 100644 --- a/gradle-plugin/plugin/src/main/resources/kotlin/data_class_opt_var.mustache +++ b/gradle-plugin/plugin/src/main/resources/kotlin/data_class_opt_var.mustache @@ -1 +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}} \ No newline at end of file + @Json(name = "{{{baseName}}}") @field:Json(name = "{{{baseName}}}") {{#vendorExtensions.x-nullable}}@XNullable {{/vendorExtensions.x-nullable}}{{#isInherited}}override {{/isInherited}}{{#hasChildren}}open {{/hasChildren}}var {{{name}}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}} = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}} \ No newline at end of file diff --git a/gradle-plugin/plugin/src/main/resources/kotlin/data_class_req_var.mustache b/gradle-plugin/plugin/src/main/resources/kotlin/data_class_req_var.mustache index 362c506a..8d916caf 100644 --- a/gradle-plugin/plugin/src/main/resources/kotlin/data_class_req_var.mustache +++ b/gradle-plugin/plugin/src/main/resources/kotlin/data_class_req_var.mustache @@ -1 +1 @@ - @Json(name = "{{{baseName}}}") @field:Json(name = "{{{baseName}}}") var {{{name}}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}} \ No newline at end of file + @Json(name = "{{{baseName}}}") @field:Json(name = "{{{baseName}}}") {{#isInherited}}override {{/isInherited}}{{#hasChildren}}open {{/hasChildren}}var {{{name}}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{/isEnum}} \ No newline at end of file From f1203895710ccafe38276078e944617c1b1d745f Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Fri, 24 Jan 2020 09:07:12 +0100 Subject: [PATCH 12/14] Plug in open_class template (template for classes with parents) --- .../java/com/yelp/codegen/SharedCodegen.kt | 8 +-- .../src/main/resources/kotlin/model.mustache | 3 +- .../main/resources/kotlin/open_class.mustache | 52 +++++++++++++++++++ 3 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 gradle-plugin/plugin/src/main/resources/kotlin/open_class.mustache 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 64e296ca..e9d8c92a 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 @@ -359,10 +359,10 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { // Ensure that after all the processing done on the CodegenModel.*Vars, hasMore does still make sense CodegenModelVar.forEachVarAttribute(codegenModel) { _, properties -> properties.fixHasMoreProperty() } - if (supportsInheritance && true == codegenModel.children?.isNotEmpty()) { - // Codegen does update the children attribute but does not keep hasChildren consistent - codegenModel.hasChildren = true - } +// if (supportsInheritance && true == codegenModel.children?.isNotEmpty()) { +// // Codegen does update the children attribute but does not keep hasChildren consistent +// codegenModel.hasChildren = true +// } if (supportsInheritance && codegenModel.parentModel != null) { val parentPropertyNames = codegenModel.parentModel.allVars.map { it.name }.toSet() diff --git a/gradle-plugin/plugin/src/main/resources/kotlin/model.mustache b/gradle-plugin/plugin/src/main/resources/kotlin/model.mustache index b5ab28bf..ad4ce703 100644 --- a/gradle-plugin/plugin/src/main/resources/kotlin/model.mustache +++ b/gradle-plugin/plugin/src/main/resources/kotlin/model.mustache @@ -4,4 +4,5 @@ package {{modelPackage}} {{/-first}}import {{import}} {{/imports}} {{#models}}{{#model}} -{{#isAlias}}{{>type_alias}}{{/isAlias}}{{^isAlias}}{{#isEnum}}{{>enum_class}}{{/isEnum}}{{^isEnum}}{{>data_class}}{{/isEnum}}{{/isAlias}}{{/model}}{{/models}} +{{#hasChildren}}{{>open_class}}{{/hasChildren}}{{^hasChildren}}{{#isAlias}}{{>type_alias}}{{/isAlias}}{{^isAlias}}{{#isEnum}}{{>enum_class}}{{/isEnum}}{{^isEnum}}{{>data_class}}{{/isEnum}}{{/isAlias}}{{/hasChildren}}{{/model}} +{{/models}} \ No newline at end of file diff --git a/gradle-plugin/plugin/src/main/resources/kotlin/open_class.mustache b/gradle-plugin/plugin/src/main/resources/kotlin/open_class.mustache new file mode 100644 index 00000000..6596ee13 --- /dev/null +++ b/gradle-plugin/plugin/src/main/resources/kotlin/open_class.mustache @@ -0,0 +1,52 @@ +/**{{#description}} + * {{{description}}}{{/description}} +{{#allVars}} + * @property {{{name}}}{{#description}} {{description}}{{/description}} +{{/allVars}} + */ +@JsonClass(generateAdapter = true) +{{#hasChildren}} +@Polymorphic( + discriminatorField = "{{discriminator}}", + discriminatedValues = [{{#children}}"{{title}}"{{^-last}}, {{/-last}}{{/children}}], + discriminatedClasses = [{{#children}}{{classname}}::class{{^-last}}, {{/-last}}{{/children}}] +) +{{/hasChildren}} +open class {{classname}}( +{{#requiredVars}} +{{>data_class_req_var}}{{^-last}}, +{{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}}, +{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^-last}}, +{{/-last}}{{/optionalVars}} +) { + override fun toString(): String { + return "{{classname}}(" +{{#allVars}} + "{{name}}=${{name}}{{^-last}},{{/-last}}" +{{/allVars}} + ")" + } + + override fun hashCode(): Int { + var resultHashCode = 0{{#allVars}} + resultHashCode = resultHashCode * 31 + this.{{name}}.optimisticHashCode(){{/allVars}} + return resultHashCode + } + + override fun equals(other: Any?): Boolean { + return if (this === other) { + true + } else { + other is {{classname}}{{#allVars}} && + this.{{name}} == other.{{name}}{{/allVars}} + } + } + +{{#vars}} +{{#isEnum}} + /**{{#description}} + * {{{description}}}{{/description}} + * Values:{{#allowableValues}} {{#enumVars}}{{&name}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}} + */ + @JsonClass(generateAdapter = false) + enum class {{enumName}}(val value: {{complexType}}) { +{{#allowableValues}}{{#enumVars}} @Json(name = {{{value}}}) {{&name}}({{{value}}}){{^-last}},{{/-last}}{{{newline}}}{{/enumVars}}{{/allowableValues}} } +{{/isEnum}}{{/vars}}} \ No newline at end of file From 7c2ca28ff7ed43dccb855f01997085dc6db174e9 Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Sun, 26 Jan 2020 12:49:54 +0100 Subject: [PATCH 13/14] Plug PolymorphicFactory into GeneratedCodeConverters Moshi creation --- .../java/com/yelp/codegen/SharedCodegen.kt | 30 +++- .../tools/GeneratedCodeConverters.kt.mustache | 1 + samples/junit-tests/junit_tests_specs.json | 137 +++++++++++++++++- .../apis/PolymorphismApi.kt | 24 +++ .../generatecodesamples/models/Animal.kt | 70 +++++++++ .../generatecodesamples/models/Bird.kt | 26 ++++ .../codegen/generatecodesamples/models/Cat.kt | 26 ++++ .../codegen/generatecodesamples/models/Dog.kt | 35 +++++ .../generatecodesamples/models/LivingThing.kt | 19 +++ .../generatecodesamples/models/Snake.kt | 36 +++++ .../tools/GeneratedCodeConverters.kt | 1 + .../junit-tests/src/main/java/swagger.json | 2 +- 12 files changed, 397 insertions(+), 10 deletions(-) create mode 100644 samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/apis/PolymorphismApi.kt create mode 100644 samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Animal.kt create mode 100644 samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Bird.kt create mode 100644 samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Cat.kt create mode 100644 samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Dog.kt create mode 100644 samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/LivingThing.kt create mode 100644 samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Snake.kt 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 e9d8c92a..8f62f483 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 @@ -356,19 +356,32 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { .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() } + if ( + supportsInheritance && + // Having a single child usually means override of description or attributes that do not change the model + // So we should NOT mark the model with hasChildren + (codegenModel.children?.size ?: 0) > 1 + ) { + + // Codegen does update the children attribute but does not keep hasChildren consistent + codegenModel.hasChildren = true + } -// if (supportsInheritance && true == codegenModel.children?.isNotEmpty()) { -// // Codegen does update the children attribute but does not keep hasChildren consistent -// codegenModel.hasChildren = true -// } + if (codegenModel.parentModel == null) { + codegenModel.allVars?.forEach { it.isInherited = false } + } if (supportsInheritance && codegenModel.parentModel != null) { val parentPropertyNames = codegenModel.parentModel.allVars.map { it.name }.toSet() // Update parentVars (as codegen does not do it while updating the parents) - codegenModel.parentVars?.addAll(codegenModel.parentModel.allVars) + codegenModel.parentVars?.addAll( + codegenModel.parentModel.allVars.map { + val clonedProperty = it.clone() + clonedProperty.isInherited = true + clonedProperty + } + ) decoupleParentProperties(codegenModel) @@ -396,6 +409,9 @@ abstract class SharedCodegen : DefaultCodegen(), CodegenConfig { // In order to avoid to consider this model as a model with enums we're re-evaluating it. codegenModel.hasEnums = codegenModel.vars.any { it.isEnum } } + + // Ensure that after all the processing done on the CodegenModel.*Vars, hasMore does still make sense + CodegenModelVar.forEachVarAttribute(codegenModel) { _, properties -> properties.fixHasMoreProperty() } } return postProcessedModels diff --git a/gradle-plugin/plugin/src/main/resources/kotlin/tools/GeneratedCodeConverters.kt.mustache b/gradle-plugin/plugin/src/main/resources/kotlin/tools/GeneratedCodeConverters.kt.mustache index e23cbeb8..d5228f01 100644 --- a/gradle-plugin/plugin/src/main/resources/kotlin/tools/GeneratedCodeConverters.kt.mustache +++ b/gradle-plugin/plugin/src/main/resources/kotlin/tools/GeneratedCodeConverters.kt.mustache @@ -7,6 +7,7 @@ import retrofit2.converter.moshi.MoshiConverterFactory object GeneratedCodeConverters { private val moshi = Moshi.Builder() .add(XNullableAdapterFactory()) + .add(PolymorphicAdapterFactory()) .add(TypesAdapterFactory()) .build() diff --git a/samples/junit-tests/junit_tests_specs.json b/samples/junit-tests/junit_tests_specs.json index 489d6eb3..5c30953d 100644 --- a/samples/junit-tests/junit_tests_specs.json +++ b/samples/junit-tests/junit_tests_specs.json @@ -1,8 +1,102 @@ { "definitions": { - "empty_model": { + "animal": { + "allOf": [ + { + "$ref": "#/definitions/living_thing" + }, + { + "discriminator": "type", + "properties": { + "classification": { + "enum": [ + "amphibian", + "bird", + "fish", + "insect", + "mammal", + "reptile" + ], + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + } + ], + "description": "This is a generic `animal`. According to the value of `type` you might have more information", + "type": "object", + "x-model": "animal" + }, + "bird": { + "allOf": [ + { + "$ref": "#/definitions/animal" + }, + { + "properties": { + "flight_hours": { + "type": "number" + } + } + } + ], + "description": "This is a specific type of `animal`: a `bird`", + "title": "Bird", "type": "object", - "x-model": "empty_model" + "x-model": "bird" + }, + "cat": { + "allOf": [ + { + "$ref": "#/definitions/animal" + }, + { + "properties": { + "age": { + "type": "number" + } + }, + "type": "object" + } + ], + "description": "This is a specific type of `animal`: a `cat`", + "type": "object", + "x-model": "Cat" + }, + "dog": { + "allOf": [ + { + "$ref": "#/definitions/animal" + }, + { + "properties": { + "size": { + "enum": [ + "small", + "medium", + "large" + ], + "type": "string" + } + }, + "required": [ + "size" + ], + "type": "object" + } + ], + "description": "This is a specific type of `animal`: a `dog`", + "type": "object", + "x-model": "dog" + }, + "empty_model": { + "type": "object" }, "format_responses": { "properties": { @@ -25,10 +119,24 @@ "type": "object", "x-model": "format_responses" }, + "living_thing": { + "description": "This is a base class of `animal`. Created to verify that multiple parents are properly dealt with", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "title": "living_thing", + "type": "object" + }, "nested_additional_properties": { "additionalProperties": { "$ref": "#/definitions/top_level_map" }, + "title": "xmodel_has_precedence_so_this_will_not_be_used", "type": "object", "x-model": "nested_additional_properties" }, @@ -145,6 +253,15 @@ "type": "object", "x-model": "reserved_keywords" }, + "snake": { + "allOf": [ + { + "$ref": "#/definitions/animal" + } + ], + "description": "This is a specific type of `animal`: a `snake`", + "type": "object" + }, "top_level_enum": { "enum": [ "TOP_LEVEL_VALUE1", @@ -400,6 +517,22 @@ "version": "1.1.0" }, "paths": { + "/check_polymorphism": { + "get": { + "operationId": "get_check_polymorphism", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/animal" + } + } + }, + "tags": [ + "polymorphism" + ] + } + }, "/empty_endpoint": { "get": { "operationId": "get_empty_endpoint", diff --git a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/apis/PolymorphismApi.kt b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/apis/PolymorphismApi.kt new file mode 100644 index 00000000..fe3be1bb --- /dev/null +++ b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/apis/PolymorphismApi.kt @@ -0,0 +1,24 @@ +/** + * NOTE: This class is auto generated by the Swagger Gradle Codegen for the following API: JUnit Tests + * + * More info on this tool is available on https://github.com/Yelp/swagger-gradle-codegen + */ + +package com.yelp.codegen.generatecodesamples.apis + +import com.yelp.codegen.generatecodesamples.models.Animal +import io.reactivex.Single +import retrofit2.http.GET +import retrofit2.http.Headers + +@JvmSuppressWildcards +interface PolymorphismApi { + /** + * The endpoint is owned by junittests service owner + */ + @Headers( + "X-Operation-ID: get_check_polymorphism" + ) + @GET("/check_polymorphism") + fun getCheckPolymorphism(): Single +} diff --git a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Animal.kt b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Animal.kt new file mode 100644 index 00000000..4ba3186b --- /dev/null +++ b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Animal.kt @@ -0,0 +1,70 @@ +/** + * NOTE: This class is auto generated by the Swagger Gradle Codegen for the following API: JUnit Tests + * + * More info on this tool is available on https://github.com/Yelp/swagger-gradle-codegen + */ + +package com.yelp.codegen.generatecodesamples.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.yelp.codegen.generatecodesamples.tools.Polymorphic +import com.yelp.codegen.generatecodesamples.tools.optimisticHashCode + +/** + * This is a generic `animal`. According to the value of `type` you might have more information + * @property name + * @property classification + * @property type + */ +@JsonClass(generateAdapter = true) +@Polymorphic( + discriminatorField = "type", + discriminatedValues = ["bird", "dog", "snake", "Cat"], + discriminatedClasses = [Bird::class, Dog::class, Snake::class, Cat::class] +) +open class Animal( + @Json(name = "type") @field:Json(name = "type") open var type: String, + @Json(name = "name") @field:Json(name = "name") open override var name: String, + @Json(name = "classification") @field:Json(name = "classification") open var classification: Animal.ClassificationEnum? = null +) { + override fun toString(): String { + return "Animal(" + + "name=$name," + + "classification=$classification," + + "type=$type" + + ")" + } + + override fun hashCode(): Int { + var resultHashCode = 0 + resultHashCode = resultHashCode * 31 + this.name.optimisticHashCode() + resultHashCode = resultHashCode * 31 + this.classification.optimisticHashCode() + resultHashCode = resultHashCode * 31 + this.type.optimisticHashCode() + return resultHashCode + } + + override fun equals(other: Any?): Boolean { + return if (this === other) { + true + } else { + other is Animal && + this.name == other.name && + this.classification == other.classification && + this.type == other.type + } + } + + /** + * Values: AMPHIBIAN, BIRD, FISH, INSECT, MAMMAL, REPTILE + */ + @JsonClass(generateAdapter = false) + enum class ClassificationEnum(val value: String) { + @Json(name = "amphibian") AMPHIBIAN("amphibian"), + @Json(name = "bird") BIRD("bird"), + @Json(name = "fish") FISH("fish"), + @Json(name = "insect") INSECT("insect"), + @Json(name = "mammal") MAMMAL("mammal"), + @Json(name = "reptile") REPTILE("reptile") + } +} diff --git a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Bird.kt b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Bird.kt new file mode 100644 index 00000000..f4c2d709 --- /dev/null +++ b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Bird.kt @@ -0,0 +1,26 @@ +/** + * NOTE: This class is auto generated by the Swagger Gradle Codegen for the following API: JUnit Tests + * + * More info on this tool is available on https://github.com/Yelp/swagger-gradle-codegen + */ + +package com.yelp.codegen.generatecodesamples.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.math.BigDecimal + +/** + * This is a specific type of `animal`: a `bird` + * @property name + * @property classification + * @property type + * @property flightHours + */ +@JsonClass(generateAdapter = true) +data class Bird( + @Json(name = "name") @field:Json(name = "name") override var name: String, + @Json(name = "type") @field:Json(name = "type") override var type: String, + @Json(name = "flight_hours") @field:Json(name = "flight_hours") var flightHours: BigDecimal? = null, + @Json(name = "classification") @field:Json(name = "classification") override var classification: Animal.ClassificationEnum? = null +) : Animal(name = name, classification = classification, type = type) diff --git a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Cat.kt b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Cat.kt new file mode 100644 index 00000000..6affe215 --- /dev/null +++ b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Cat.kt @@ -0,0 +1,26 @@ +/** + * NOTE: This class is auto generated by the Swagger Gradle Codegen for the following API: JUnit Tests + * + * More info on this tool is available on https://github.com/Yelp/swagger-gradle-codegen + */ + +package com.yelp.codegen.generatecodesamples.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import java.math.BigDecimal + +/** + * This is a specific type of `animal`: a `cat` + * @property name + * @property classification + * @property type + * @property age + */ +@JsonClass(generateAdapter = true) +data class Cat( + @Json(name = "name") @field:Json(name = "name") override var name: String, + @Json(name = "type") @field:Json(name = "type") override var type: String, + @Json(name = "age") @field:Json(name = "age") var age: BigDecimal? = null, + @Json(name = "classification") @field:Json(name = "classification") override var classification: Animal.ClassificationEnum? = null +) : Animal(name = name, classification = classification, type = type) diff --git a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Dog.kt b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Dog.kt new file mode 100644 index 00000000..2d4f472b --- /dev/null +++ b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Dog.kt @@ -0,0 +1,35 @@ +/** + * NOTE: This class is auto generated by the Swagger Gradle Codegen for the following API: JUnit Tests + * + * More info on this tool is available on https://github.com/Yelp/swagger-gradle-codegen + */ + +package com.yelp.codegen.generatecodesamples.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * This is a specific type of `animal`: a `dog` + * @property name + * @property classification + * @property type + * @property size + */ +@JsonClass(generateAdapter = true) +data class Dog( + @Json(name = "size") @field:Json(name = "size") var size: Dog.SizeEnum, + @Json(name = "name") @field:Json(name = "name") override var name: String, + @Json(name = "type") @field:Json(name = "type") override var type: String, + @Json(name = "classification") @field:Json(name = "classification") override var classification: Animal.ClassificationEnum? = null +) : Animal(name = name, classification = classification, type = type) { + /** + * Values: SMALL, MEDIUM, LARGE + */ + @JsonClass(generateAdapter = false) + enum class SizeEnum(val value: String) { + @Json(name = "small") SMALL("small"), + @Json(name = "medium") MEDIUM("medium"), + @Json(name = "large") LARGE("large") + } +} diff --git a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/LivingThing.kt b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/LivingThing.kt new file mode 100644 index 00000000..3f088258 --- /dev/null +++ b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/LivingThing.kt @@ -0,0 +1,19 @@ +/** + * NOTE: This class is auto generated by the Swagger Gradle Codegen for the following API: JUnit Tests + * + * More info on this tool is available on https://github.com/Yelp/swagger-gradle-codegen + */ + +package com.yelp.codegen.generatecodesamples.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * This is a base class of `animal`. Created to verify that multiple parents are properly dealt with + * @property name + */ +@JsonClass(generateAdapter = true) +data class LivingThing( + @Json(name = "name") @field:Json(name = "name") var name: String +) diff --git a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Snake.kt b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Snake.kt new file mode 100644 index 00000000..5cd08b0b --- /dev/null +++ b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/models/Snake.kt @@ -0,0 +1,36 @@ +/** + * NOTE: This class is auto generated by the Swagger Gradle Codegen for the following API: JUnit Tests + * + * More info on this tool is available on https://github.com/Yelp/swagger-gradle-codegen + */ + +package com.yelp.codegen.generatecodesamples.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * This is a specific type of `animal`: a `snake` + * @property name + * @property classification + * @property type + */ +@JsonClass(generateAdapter = true) +data class Snake( + @Json(name = "name") @field:Json(name = "name") override var name: String, + @Json(name = "type") @field:Json(name = "type") override var type: String, + @Json(name = "classification") @field:Json(name = "classification") override var classification: Animal.ClassificationEnum? = null +) : Animal(name = name, classification = classification, type = type) { + /** + * Values: AMPHIBIAN, BIRD, FISH, INSECT, MAMMAL, REPTILE + */ + @JsonClass(generateAdapter = false) + enum class ClassificationEnum(val value: String) { + @Json(name = "amphibian") AMPHIBIAN("amphibian"), + @Json(name = "bird") BIRD("bird"), + @Json(name = "fish") FISH("fish"), + @Json(name = "insect") INSECT("insect"), + @Json(name = "mammal") MAMMAL("mammal"), + @Json(name = "reptile") REPTILE("reptile") + } +} diff --git a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/tools/GeneratedCodeConverters.kt b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/tools/GeneratedCodeConverters.kt index 09ef3b3b..cb7bd5c4 100644 --- a/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/tools/GeneratedCodeConverters.kt +++ b/samples/junit-tests/src/main/java/com/yelp/codegen/generatecodesamples/tools/GeneratedCodeConverters.kt @@ -7,6 +7,7 @@ import retrofit2.converter.moshi.MoshiConverterFactory object GeneratedCodeConverters { private val moshi = Moshi.Builder() .add(XNullableAdapterFactory()) + .add(PolymorphicAdapterFactory()) .add(TypesAdapterFactory()) .build() diff --git a/samples/junit-tests/src/main/java/swagger.json b/samples/junit-tests/src/main/java/swagger.json index ac38d787..12af846e 100644 --- a/samples/junit-tests/src/main/java/swagger.json +++ b/samples/junit-tests/src/main/java/swagger.json @@ -1 +1 @@ -{"swagger":"2.0","info":{"description":"This spec is used to have JUnit Tests to check the generated code.","version":"1.1.0","title":"JUnit Tests"},"produces":["application/json"],"paths":{"/empty_endpoint":{"get":{"tags":["resource"],"operationId":"get_empty_endpoint","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/empty_model"}}}}},"/format_endpoint/{property_format}":{"get":{"tags":["resource"],"operationId":"get_format_endpoint","parameters":[{"name":"property_format","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/format_responses"}}}}},"/model_name/no_x-model_or_title":{"get":{"tags":["model_name_check"],"operationId":"getInlinedModelWithNoNames","parameters":[],"responses":{"200":{"description":"The expected model name is InlineResponse200 (generated by codegen)","schema":{"type":"object","properties":{"no_xmodel_no_title":{"type":"string","enum":["val1","val2"]}}}}}}},"/model_name/title_only":{"get":{"tags":["model_name_check"],"operationId":"getInlinedModelWithTitleOnly","parameters":[],"responses":{"200":{"description":"The expected model name is ModelWithOnlyTitle (title attribute)","schema":{"type":"object","properties":{"title_only":{"type":"string","enum":["val1","val2"]}},"title":"ModelWithOnlyTitle"}}}}},"/model_name/x-model_and_title":{"get":{"tags":["model_name_check"],"operationId":"getInlinedModelWithXModelAndTitle","parameters":[],"responses":{"200":{"description":"The expected model name is ModelWithXModelAndTitle (x-model attribute)","schema":{"type":"object","properties":{"xmodel_and_title":{"type":"string","enum":["val1","val2"]}},"title":"ThisShouldBeIgnored","x-model":"ModelWithXModelAndTitle"}}}}},"/model_name/x-model_only":{"get":{"tags":["model_name_check"],"operationId":"getInlinedModelWithXModelOnly","parameters":[],"responses":{"200":{"description":"The expected model name is ModelWithOnlyXModel (x-model attribute)","schema":{"type":"object","properties":{"xmodel_only":{"type":"string","enum":["val1","val2"]}},"x-model":"ModelWithOnlyXModel"}}}}},"/nested_additional_properties":{"get":{"tags":["resource"],"operationId":"get_nested_additional_properties","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/nested_additional_properties"}}}}},"/nested_additional_properties/custom_description":{"get":{"tags":["resource"],"operationId":"get_nested_additional_properties_custom_description","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/nested_additional_properties_custom_description"}}}}},"/post/file":{"post":{"tags":["file"],"operationId":"post_file","consumes":["multipart/form-data"],"parameters":[{"name":"client_file","in":"formData","required":true,"type":"file"}],"responses":{"default":{"description":"Something"}}}},"/post/file_without_consumes":{"post":{"tags":["file"],"operationId":"post_file_without_multipart_form_data","parameters":[{"name":"client_file","in":"formData","required":true,"type":"file"}],"responses":{"default":{"description":"Something"}}}},"/post/multiple_files":{"post":{"tags":["file"],"operationId":"post_multiple_files","consumes":["multipart/form-data"],"parameters":[{"name":"client_file","in":"formData","required":true,"type":"file"},{"name":"certificate_file","in":"formData","required":true,"type":"file"}],"responses":{"default":{"description":"Something"}}}},"/property_array/{value_type}/{size}":{"get":{"tags":["resource"],"operationId":"get_property_array","parameters":[{"name":"value_type","in":"path","required":true,"type":"string"},{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/property_array"}}}}},"/property_map/{value_type}/{size}":{"get":{"tags":["resource"],"operationId":"get_property_map","parameters":[{"name":"value_type","in":"path","required":true,"type":"string"},{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/property_map"}}}}},"/required/type_endpoint":{"get":{"tags":["resource"],"operationId":"get_required_type_endpoint","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/required_type_responses"}}}}},"/reserved_keywords":{"get":{"tags":["resource"],"operationId":"get_reserved_keywords","parameters":[{"name":"class","in":"query","required":false,"type":"string"},{"name":"data","in":"query","required":false,"type":"string"},{"name":"for","in":"query","required":false,"type":"string"},{"name":"operator","in":"query","required":false,"type":"string"},{"name":"val","in":"query","required":false,"type":"string"},{"name":"var","in":"query","required":false,"type":"string"},{"name":"when","in":"query","required":false,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/reserved_keywords"}}}}},"/symbols/in/parameter/name":{"get":{"tags":["resource"],"summary":"Test symbols in parameter name","description":"Make sure that symbols in parameter name are treated properly","operationId":"getSymbolsInParameterName","parameters":[{"name":"parameter","in":"query","required":false,"type":"string"},{"name":"brackets[]","in":"query","required":false,"type":"string"},{"name":"brackets[withText]","in":"query","required":false,"type":"string"},{"name":"dot.","in":"query","required":false,"type":"string"},{"name":"dot.withText","in":"query","required":false,"type":"string"}],"responses":{"200":{"description":"successful operation"}}}},"/top_level_enum":{"get":{"tags":["resource"],"operationId":"get_top_level_enum","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/top_level_enum"}}}}},"/top_level_enum/nested":{"get":{"tags":["resource"],"operationId":"get_top_level_enum_nested","parameters":[],"responses":{"200":{"description":"","schema":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"$ref":"#/definitions/top_level_enum"}}}}}}},"/top_level_map/{size}":{"get":{"tags":["resource"],"operationId":"get_top_level_map","parameters":[{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/top_level_map"}}}}},"/type_endpoint/{property_type}":{"get":{"tags":["resource"],"operationId":"get_type_endpoint","parameters":[{"name":"property_type","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/type_responses"}}}}},"/xnullable/format_endpoint/{property_format}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_format_endpoint","parameters":[{"name":"property_format","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_format_responses"}}}}},"/xnullable/nested_additional_properties":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_nested_additional_properties","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_nested_additional_properties"}}}}},"/xnullable/property_array/{value_type}/{size}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_property_array","parameters":[{"name":"value_type","in":"path","required":true,"type":"string"},{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_property_array"}}}}},"/xnullable/property_map/{value_type}/{size}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_property_map","parameters":[{"name":"value_type","in":"path","required":true,"type":"string"},{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_property_map"}}}}},"/xnullable/required/property_array/{size}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_required_property_array","parameters":[{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_required_property_array"}}}}},"/xnullable/required/property_map/{size}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_required_property_map","parameters":[{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_required_property_map"}}}}},"/xnullable/required/type_endpoint/{property_type}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_required_type_endpoint","parameters":[{"name":"property_type","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_required_type_responses"}}}}},"/xnullable/type_endpoint/{property_type}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_type_endpoint","parameters":[{"name":"property_type","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_type_responses"}}}}}},"definitions":{"empty_model":{"type":"object","x-model":"empty_model"},"format_responses":{"type":"object","properties":{"date_property":{"type":"string","format":"date"},"datetime_property":{"type":"string","format":"date-time"},"enum_property":{"type":"string","enum":["VALUE1","VALUE2"]}},"x-model":"format_responses"},"nested_additional_properties":{"type":"object","additionalProperties":{"$ref":"#/definitions/top_level_map"},"x-model":"nested_additional_properties"},"nested_additional_properties_custom_description":{"allOf":[{"$ref":"#/definitions/nested_additional_properties"},{"description":"This is a workaroud to override the description provided by nested_additional_properties"}],"x-model":"nested_additional_properties_custom_description"},"property_array":{"type":"object","properties":{"number_array":{"type":"array","items":{"type":"number"}},"string_array":{"type":"array","items":{"type":"string"}}},"x-model":"property_array"},"property_map":{"type":"object","properties":{"number_map":{"type":"object","additionalProperties":{"type":"number"},"x-model":"number_map"},"object_map":{"type":"object","properties":{},"x-model":"object_map"},"string_map":{"type":"object","additionalProperties":{"type":"string"},"x-model":"string_map"}},"x-model":"property_map"},"required_type_responses":{"type":"object","required":["boolean_property","enum_property","integer_property","number_property","string_property"],"properties":{"boolean_property":{"type":"boolean"},"enum_property":{"type":"string","enum":["VALUE1","VALUE2"]},"integer_property":{"type":"integer"},"number_property":{"type":"number"},"string_property":{"type":"string"}},"x-model":"required_type_responses"},"reserved_keywords":{"type":"object","properties":{"class":{"type":"string"},"data":{"type":"string"},"for":{"type":"string"},"operator":{"type":"string"},"val":{"type":"string"},"var":{"type":"string"},"when":{"type":"string"}},"x-model":"reserved_keywords"},"top_level_enum":{"type":"string","enum":["TOP_LEVEL_VALUE1","TOP_LEVEL_VALUE2"],"x-model":"top_level_enum"},"top_level_map":{"type":"object","additionalProperties":{"type":"string"},"x-model":"top_level_map"},"type_responses":{"type":"object","properties":{"boolean_property":{"type":"boolean"},"integer_property":{"type":"integer"},"number_property":{"type":"number"},"string_property":{"type":"string"}},"x-model":"type_responses"},"xnullable_format_responses":{"type":"object","properties":{"date_property":{"type":"string","format":"date","x-nullable":true},"datetime_property":{"type":"string","format":"date-time","x-nullable":true},"double_property":{"type":"number","format":"double","x-nullable":true}},"x-model":"xnullable_format_responses"},"xnullable_map":{"type":"object","additionalProperties":{"type":"string","x-nullable":true},"x-model":"xnullable_map","x-nullable":true},"xnullable_nested_additional_properties":{"type":"object","additionalProperties":{"$ref":"#/definitions/xnullable_map"},"x-model":"xnullable_nested_additional_properties"},"xnullable_property_array":{"type":"object","properties":{"number_array":{"type":"array","items":{"type":"number","x-nullable":true},"x-nullable":true},"string_array":{"type":"array","items":{"type":"string","x-nullable":true},"x-nullable":true}},"x-model":"xnullable_property_array"},"xnullable_property_map":{"type":"object","properties":{"number_map":{"type":"object","additionalProperties":{"type":"number","x-nullable":true},"x-model":"number_map","x-nullable":true},"object_map":{"type":"object","properties":{},"x-model":"object_map","x-nullable":true},"string_map":{"type":"object","additionalProperties":{"type":"string","x-nullable":true},"x-model":"string_map","x-nullable":true}},"x-model":"xnullable_property_map"},"xnullable_required_property_array":{"type":"object","required":["number_array","string_array"],"properties":{"number_array":{"type":"array","items":{"type":"number","x-nullable":true},"x-nullable":true},"string_array":{"type":"array","items":{"type":"string","x-nullable":true},"x-nullable":true}},"x-model":"xnullable_required_property_array"},"xnullable_required_property_map":{"type":"object","required":["number_map","object_map","string_map"],"properties":{"number_map":{"type":"object","additionalProperties":{"type":"number","x-nullable":true},"x-model":"number_map","x-nullable":true},"object_map":{"type":"object","properties":{},"x-model":"object_map","x-nullable":true},"string_map":{"type":"object","additionalProperties":{"type":"string","x-nullable":true},"x-model":"string_map","x-nullable":true}},"x-model":"xnullable_required_property_map"},"xnullable_required_type_responses":{"type":"object","required":["boolean_property","enum_property","integer_property","number_property","string_property"],"properties":{"boolean_property":{"type":"boolean","x-nullable":true},"enum_property":{"type":"string","enum":["VALUE1","VALUE2"],"x-nullable":true},"integer_property":{"type":"integer","x-nullable":true},"number_property":{"type":"number","x-nullable":true},"string_property":{"type":"string","x-nullable":true}},"x-model":"xnullable_required_type_responses"},"xnullable_type_responses":{"type":"object","properties":{"boolean_property":{"type":"boolean","x-nullable":true},"enum_property":{"type":"string","enum":["VALUE1","VALUE2"],"x-nullable":true},"integer_property":{"type":"integer","x-nullable":true},"number_property":{"type":"number","x-nullable":true},"string_property":{"type":"string","x-nullable":true}},"x-model":"xnullable_type_responses"}}} \ No newline at end of file +{"swagger":"2.0","info":{"description":"This spec is used to have JUnit Tests to check the generated code.","version":"1.1.0","title":"JUnit Tests"},"produces":["application/json"],"paths":{"/check_polymorphism":{"get":{"tags":["polymorphism"],"operationId":"get_check_polymorphism","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/animal"}}}}},"/empty_endpoint":{"get":{"tags":["resource"],"operationId":"get_empty_endpoint","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/empty_model"}}}}},"/format_endpoint/{property_format}":{"get":{"tags":["resource"],"operationId":"get_format_endpoint","parameters":[{"name":"property_format","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/format_responses"}}}}},"/model_name/no_x-model_or_title":{"get":{"tags":["model_name_check"],"operationId":"getInlinedModelWithNoNames","parameters":[],"responses":{"200":{"description":"The expected model name is InlineResponse200 (generated by codegen)","schema":{"type":"object","properties":{"no_xmodel_no_title":{"type":"string","enum":["val1","val2"]}}}}}}},"/model_name/title_only":{"get":{"tags":["model_name_check"],"operationId":"getInlinedModelWithTitleOnly","parameters":[],"responses":{"200":{"description":"The expected model name is ModelWithOnlyTitle (title attribute)","schema":{"type":"object","properties":{"title_only":{"type":"string","enum":["val1","val2"]}},"title":"ModelWithOnlyTitle"}}}}},"/model_name/x-model_and_title":{"get":{"tags":["model_name_check"],"operationId":"getInlinedModelWithXModelAndTitle","parameters":[],"responses":{"200":{"description":"The expected model name is ModelWithXModelAndTitle (x-model attribute)","schema":{"type":"object","properties":{"xmodel_and_title":{"type":"string","enum":["val1","val2"]}},"title":"ThisShouldBeIgnored","x-model":"ModelWithXModelAndTitle"}}}}},"/model_name/x-model_only":{"get":{"tags":["model_name_check"],"operationId":"getInlinedModelWithXModelOnly","parameters":[],"responses":{"200":{"description":"The expected model name is ModelWithOnlyXModel (x-model attribute)","schema":{"type":"object","properties":{"xmodel_only":{"type":"string","enum":["val1","val2"]}},"x-model":"ModelWithOnlyXModel"}}}}},"/nested_additional_properties":{"get":{"tags":["resource"],"operationId":"get_nested_additional_properties","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/nested_additional_properties"}}}}},"/nested_additional_properties/custom_description":{"get":{"tags":["resource"],"operationId":"get_nested_additional_properties_custom_description","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/nested_additional_properties_custom_description"}}}}},"/post/file":{"post":{"tags":["file"],"operationId":"post_file","consumes":["multipart/form-data"],"parameters":[{"name":"client_file","in":"formData","required":true,"type":"file"}],"responses":{"default":{"description":"Something"}}}},"/post/file_without_consumes":{"post":{"tags":["file"],"operationId":"post_file_without_multipart_form_data","parameters":[{"name":"client_file","in":"formData","required":true,"type":"file"}],"responses":{"default":{"description":"Something"}}}},"/post/multiple_files":{"post":{"tags":["file"],"operationId":"post_multiple_files","consumes":["multipart/form-data"],"parameters":[{"name":"client_file","in":"formData","required":true,"type":"file"},{"name":"certificate_file","in":"formData","required":true,"type":"file"}],"responses":{"default":{"description":"Something"}}}},"/property_array/{value_type}/{size}":{"get":{"tags":["resource"],"operationId":"get_property_array","parameters":[{"name":"value_type","in":"path","required":true,"type":"string"},{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/property_array"}}}}},"/property_map/{value_type}/{size}":{"get":{"tags":["resource"],"operationId":"get_property_map","parameters":[{"name":"value_type","in":"path","required":true,"type":"string"},{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/property_map"}}}}},"/required/type_endpoint":{"get":{"tags":["resource"],"operationId":"get_required_type_endpoint","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/required_type_responses"}}}}},"/reserved_keywords":{"get":{"tags":["resource"],"operationId":"get_reserved_keywords","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/reserved_keywords"}}}}},"/symbols/in/parameter/name":{"get":{"tags":["resource"],"summary":"Test symbols in parameter name","description":"Make sure that symbols in parameter name are treated properly","operationId":"getSymbolsInParameterName","parameters":[{"name":"parameter","in":"query","required":false,"type":"string"},{"name":"brackets[]","in":"query","required":false,"type":"string"},{"name":"brackets[withText]","in":"query","required":false,"type":"string"},{"name":"dot.","in":"query","required":false,"type":"string"},{"name":"dot.withText","in":"query","required":false,"type":"string"}],"responses":{"200":{"description":"successful operation"}}}},"/top_level_enum":{"get":{"tags":["resource"],"operationId":"get_top_level_enum","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/top_level_enum"}}}}},"/top_level_enum/nested":{"get":{"tags":["resource"],"operationId":"get_top_level_enum_nested","parameters":[],"responses":{"200":{"description":"","schema":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{"$ref":"#/definitions/top_level_enum"}}}}}}},"/top_level_map/{size}":{"get":{"tags":["resource"],"operationId":"get_top_level_map","parameters":[{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/top_level_map"}}}}},"/type_endpoint/{property_type}":{"get":{"tags":["resource"],"operationId":"get_type_endpoint","parameters":[{"name":"property_type","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/type_responses"}}}}},"/xnullable/format_endpoint/{property_format}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_format_endpoint","parameters":[{"name":"property_format","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_format_responses"}}}}},"/xnullable/nested_additional_properties":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_nested_additional_properties","parameters":[],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_nested_additional_properties"}}}}},"/xnullable/property_array/{value_type}/{size}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_property_array","parameters":[{"name":"value_type","in":"path","required":true,"type":"string"},{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_property_array"}}}}},"/xnullable/property_map/{value_type}/{size}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_property_map","parameters":[{"name":"value_type","in":"path","required":true,"type":"string"},{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_property_map"}}}}},"/xnullable/required/property_array/{size}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_required_property_array","parameters":[{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_required_property_array"}}}}},"/xnullable/required/property_map/{size}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_required_property_map","parameters":[{"name":"size","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_required_property_map"}}}}},"/xnullable/required/type_endpoint/{property_type}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_required_type_endpoint","parameters":[{"name":"property_type","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_required_type_responses"}}}}},"/xnullable/type_endpoint/{property_type}":{"get":{"tags":["xnullable"],"operationId":"get_xnullable_type_endpoint","parameters":[{"name":"property_type","in":"path","required":true,"type":"string"}],"responses":{"200":{"description":"","schema":{"$ref":"#/definitions/xnullable_type_responses"}}}}}},"definitions":{"animal":{"allOf":[{"$ref":"#/definitions/living_thing"},{"type":"object","required":["type"],"discriminator":"type","properties":{"classification":{"type":"string","enum":["amphibian","bird","fish","insect","mammal","reptile"]},"type":{"type":"string"}}}],"description":"This is a generic `animal`. According to the value of `type` you might have more information","x-model":"animal"},"bird":{"title":"Bird","allOf":[{"$ref":"#/definitions/animal"},{"properties":{"flight_hours":{"type":"number"}}}],"description":"This is a specific type of `animal`: a `bird`","x-model":"bird"},"cat":{"allOf":[{"$ref":"#/definitions/animal"},{"type":"object","properties":{"age":{"type":"number"}}}],"description":"This is a specific type of `animal`: a `cat`","x-model":"Cat"},"dog":{"allOf":[{"$ref":"#/definitions/animal"},{"type":"object","required":["size"],"properties":{"size":{"type":"string","enum":["small","medium","large"]}}}],"description":"This is a specific type of `animal`: a `dog`","x-model":"dog"},"empty_model":{"type":"object"},"format_responses":{"type":"object","properties":{"date_property":{"type":"string","format":"date"},"datetime_property":{"type":"string","format":"date-time"},"enum_property":{"type":"string","enum":["VALUE1","VALUE2"]}},"x-model":"format_responses"},"living_thing":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}},"title":"living_thing","description":"This is a base class of `animal`. Created to verify that multiple parents are properly dealt with"},"nested_additional_properties":{"type":"object","title":"xmodel_has_precedence_so_this_will_not_be_used","additionalProperties":{"$ref":"#/definitions/top_level_map"},"x-model":"nested_additional_properties"},"nested_additional_properties_custom_description":{"allOf":[{"$ref":"#/definitions/nested_additional_properties"},{"description":"This is a workaroud to override the description provided by nested_additional_properties"}],"x-model":"nested_additional_properties_custom_description"},"property_array":{"type":"object","properties":{"number_array":{"type":"array","items":{"type":"number"}},"string_array":{"type":"array","items":{"type":"string"}}},"x-model":"property_array"},"property_map":{"type":"object","properties":{"number_map":{"type":"object","additionalProperties":{"type":"number"},"x-model":"number_map"},"object_map":{"type":"object","properties":{},"x-model":"object_map"},"string_map":{"type":"object","additionalProperties":{"type":"string"},"x-model":"string_map"}},"x-model":"property_map"},"required_type_responses":{"type":"object","required":["boolean_property","enum_property","integer_property","number_property","string_property"],"properties":{"boolean_property":{"type":"boolean"},"enum_property":{"type":"string","enum":["VALUE1","VALUE2"]},"integer_property":{"type":"integer"},"number_property":{"type":"number"},"string_property":{"type":"string"}},"x-model":"required_type_responses"},"reserved_keywords":{"type":"object","properties":{"class":{"type":"string"},"for":{"type":"string"},"operator":{"type":"string"},"val":{"type":"string"},"var":{"type":"string"},"when":{"type":"string"}},"x-model":"reserved_keywords"},"snake":{"allOf":[{"$ref":"#/definitions/animal"}],"description":"This is a specific type of `animal`: a `snake`"},"top_level_enum":{"type":"string","enum":["TOP_LEVEL_VALUE1","TOP_LEVEL_VALUE2"],"x-model":"top_level_enum"},"top_level_map":{"type":"object","additionalProperties":{"type":"string"},"x-model":"top_level_map"},"type_responses":{"type":"object","properties":{"boolean_property":{"type":"boolean"},"integer_property":{"type":"integer"},"number_property":{"type":"number"},"string_property":{"type":"string"}},"x-model":"type_responses"},"xnullable_format_responses":{"type":"object","properties":{"date_property":{"type":"string","format":"date","x-nullable":true},"datetime_property":{"type":"string","format":"date-time","x-nullable":true},"double_property":{"type":"number","format":"double","x-nullable":true}},"x-model":"xnullable_format_responses"},"xnullable_map":{"type":"object","additionalProperties":{"type":"string","x-nullable":true},"x-model":"xnullable_map","x-nullable":true},"xnullable_nested_additional_properties":{"type":"object","additionalProperties":{"$ref":"#/definitions/xnullable_map"},"x-model":"xnullable_nested_additional_properties"},"xnullable_property_array":{"type":"object","properties":{"number_array":{"type":"array","items":{"type":"number","x-nullable":true},"x-nullable":true},"string_array":{"type":"array","items":{"type":"string","x-nullable":true},"x-nullable":true}},"x-model":"xnullable_property_array"},"xnullable_property_map":{"type":"object","properties":{"number_map":{"type":"object","additionalProperties":{"type":"number","x-nullable":true},"x-model":"number_map","x-nullable":true},"object_map":{"type":"object","properties":{},"x-model":"object_map","x-nullable":true},"string_map":{"type":"object","additionalProperties":{"type":"string","x-nullable":true},"x-model":"string_map","x-nullable":true}},"x-model":"xnullable_property_map"},"xnullable_required_property_array":{"type":"object","required":["number_array","string_array"],"properties":{"number_array":{"type":"array","items":{"type":"number","x-nullable":true},"x-nullable":true},"string_array":{"type":"array","items":{"type":"string","x-nullable":true},"x-nullable":true}},"x-model":"xnullable_required_property_array"},"xnullable_required_property_map":{"type":"object","required":["number_map","object_map","string_map"],"properties":{"number_map":{"type":"object","additionalProperties":{"type":"number","x-nullable":true},"x-model":"number_map","x-nullable":true},"object_map":{"type":"object","properties":{},"x-model":"object_map","x-nullable":true},"string_map":{"type":"object","additionalProperties":{"type":"string","x-nullable":true},"x-model":"string_map","x-nullable":true}},"x-model":"xnullable_required_property_map"},"xnullable_required_type_responses":{"type":"object","required":["boolean_property","enum_property","integer_property","number_property","string_property"],"properties":{"boolean_property":{"type":"boolean","x-nullable":true},"enum_property":{"type":"string","enum":["VALUE1","VALUE2"],"x-nullable":true},"integer_property":{"type":"integer","x-nullable":true},"number_property":{"type":"number","x-nullable":true},"string_property":{"type":"string","x-nullable":true}},"x-model":"xnullable_required_type_responses"},"xnullable_type_responses":{"type":"object","properties":{"boolean_property":{"type":"boolean","x-nullable":true},"enum_property":{"type":"string","enum":["VALUE1","VALUE2"],"x-nullable":true},"integer_property":{"type":"integer","x-nullable":true},"number_property":{"type":"number","x-nullable":true},"string_property":{"type":"string","x-nullable":true}},"x-model":"xnullable_type_responses"}}} From b12f0b455a38c4c785a13663df29bc5101a919c7 Mon Sep 17 00:00:00 2001 From: Samuele Maci Date: Sun, 26 Jan 2020 17:37:34 +0100 Subject: [PATCH 14/14] Add JUnitTests covering the Polymorphic support --- .../PolymorphismEndpointTest.kt | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 samples/junit-tests/src/test/java/com/yelp/codegen/generatecodesamples/PolymorphismEndpointTest.kt diff --git a/samples/junit-tests/src/test/java/com/yelp/codegen/generatecodesamples/PolymorphismEndpointTest.kt b/samples/junit-tests/src/test/java/com/yelp/codegen/generatecodesamples/PolymorphismEndpointTest.kt new file mode 100644 index 00000000..7fd70b8b --- /dev/null +++ b/samples/junit-tests/src/test/java/com/yelp/codegen/generatecodesamples/PolymorphismEndpointTest.kt @@ -0,0 +1,167 @@ +package com.yelp.codegen.generatecodesamples + +import com.squareup.moshi.JsonDataException +import com.yelp.codegen.generatecodesamples.apis.PolymorphismApi +import com.yelp.codegen.generatecodesamples.models.Animal +import com.yelp.codegen.generatecodesamples.models.Cat +import com.yelp.codegen.generatecodesamples.models.Dog +import com.yelp.codegen.generatecodesamples.tools.MockServerApiRule +import okhttp3.mockwebserver.MockResponse +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test + +class PolymorphismEndpointTest { + + @get:Rule + val rule = MockServerApiRule() + + @Test + fun checkPolymorphicReturnsBaseModel() { + rule.server.enqueue( + MockResponse().setBody( + """ + { + "name": "a name", + "type": "animal" + } + """.trimIndent() + ) + ) + + val returned = rule.getApi().getCheckPolymorphism().blockingGet() + assertEquals( + Animal( + name = "a name", + type = "animal", + classification = null + ), + returned + ) + assertTrue(returned is Animal) + assertTrue(returned !is Cat) + assertTrue(returned !is Dog) + } + + @Test + fun checkPolymorphicReturnsBaseModelWhenUnknownDiscriminator() { + rule.server.enqueue( + MockResponse().setBody( + """ + { + "name": "a name", + "type": "snake", + "classification": "reptile" + } + """.trimIndent() + ) + ) + + val returned = rule.getApi().getCheckPolymorphism().blockingGet() + assertEquals( + Animal( + name = "a name", + type = "snake", + classification = Animal.ClassificationEnum.REPTILE + ), + returned + ) + assertTrue(returned is Animal) + assertTrue(returned !is Cat) + assertTrue(returned !is Dog) + } + + @Test + fun checkPolymorphicReturnsDiscriminatedModelWhenRecognized() { + rule.server.enqueue( + MockResponse().setBody( + """ + { + "name": "a name", + "type": "Cat", + "classification": "mammal", + "age": 42 + } + """.trimIndent() + ) + ) + + val returned = rule.getApi().getCheckPolymorphism().blockingGet() + assertEquals( + Animal( + name = "a name", + type = "Cat", + classification = Animal.ClassificationEnum.MAMMAL + ), + returned + ) + assertEquals( + Cat( + name = "a name", + type = "Cat", + classification = Animal.ClassificationEnum.MAMMAL, + age = 42.toBigDecimal() + ), + returned + ) + assertTrue(returned is Animal) + assertTrue(returned is Cat) + assertTrue(returned !is Dog) + } + + @Test + fun checkPolymorphicReturnsDiscriminatedModelWhenRecognizedAndInnerModelHasEnum() { + rule.server.enqueue( + MockResponse().setBody( + """ + { + "name": "a name", + "type": "dog", + "classification": "mammal", + "size": "small" + } + """.trimIndent() + ) + ) + + val returned = rule.getApi().getCheckPolymorphism().blockingGet() + assertEquals( + Animal( + name = "a name", + type = "dog", + classification = Animal.ClassificationEnum.MAMMAL + ), + returned + ) + assertEquals( + Dog( + name = "a name", + type = "dog", + classification = Animal.ClassificationEnum.MAMMAL, + size = Dog.SizeEnum.SMALL + ), + returned + ) + assertTrue(returned is Animal) + assertTrue(returned !is Cat) + assertTrue(returned is Dog) + } + + @Test(expected = JsonDataException::class) + fun checkPolymorphicDoesNotReturnBaseModelIfDiscriminatedModelIsIdentifiedAnFailedToParse() { + rule.server.enqueue( + MockResponse().setBody( + """ + { + "name": "a name", + "type": "dog", + "classification": "mammal" + } + """.trimIndent() + ) + ) + + rule.getApi().getCheckPolymorphism().blockingGet() + } +}