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..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 } /* @@ -125,6 +127,8 @@ open class KotlinGenerator : SharedCodegen() { "CollectionFormats.kt", "EnumToValueConverterFactory.kt", "GeneratedCodeConverters.kt", + "OptimisticHashCode.kt", + "PolymorphicAdapterFactory.kt", "TypesAdapters.kt", "WrapperConverterFactory.kt", "XNullable.kt", @@ -202,14 +206,13 @@ 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") } - 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") @@ -222,34 +225,11 @@ open class KotlinGenerator : SharedCodegen() { break } } - } - - 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 + 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 d717ad23..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 @@ -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,27 @@ 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) } + } + + // 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 } @@ -283,6 +307,116 @@ 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) + + // 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 -> + 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 (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.map { + val clonedProperty = it.clone() + clonedProperty.isInherited = true + clonedProperty + } + ) + + 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 } + } + + // 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 +502,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 +546,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 +696,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 +725,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 + } + } +} 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/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 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 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/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/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/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 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/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) + } + } +} 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"}}} 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() + } +}