Issue 1025: Kotlin generator doesn't support inheritance (#1026)

* allVars is duplicating child preoperties when models are inherited. Filter out these duplicates in the KotlinSpringServerCodegen
  * isInherited property was not being populated in the CodegenModel, re-parse the models in the KotlinSpringServerCodegen class and populate the property here
  * Add template support for Kotlin models which require inheritance from a base class to support oneOf declarations in the api yaml
  * Change optional for parameters to use Kotlins nullable
  * Update petstore api with Optional -> Nullable
This commit is contained in:
Brian Mooney 2018-09-19 11:26:47 +01:00 committed by Adam Drakeford
parent 78fae0ea49
commit b7edad5cd0
14 changed files with 60 additions and 21 deletions

View File

@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableMap;
import com.samskivert.mustache.Mustache;
import com.samskivert.mustache.Template;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema;
import org.openapitools.codegen.*;
import org.openapitools.codegen.languages.features.BeanValidationFeatures;
import org.openapitools.codegen.utils.URLPathUtils;
@ -32,6 +33,7 @@ import java.io.Writer;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
@ -386,7 +388,6 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
if (Boolean.TRUE.equals(model.hasEnums)) {
model.imports.add("JsonValue");
}
} else {
//Needed imports for Jackson's JsonCreator
if (additionalProperties.containsKey("jackson")) {
@ -394,6 +395,9 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
}
}
if (model.discriminator != null && additionalProperties.containsKey("jackson")) {
model.imports.addAll(Arrays.asList("JsonSubTypes", "JsonTypeInfo"));
}
}
@Override
@ -513,4 +517,16 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
writer.write(fragment.execute().replaceAll(from, to));
}
}
// Can't figure out the logic in DefaultCodegen but optional vars are getting duplicated when there's
// inheritance involved. Also, isInherited doesn't seem to be getting set properly ¯\_()_/¯
@Override
public CodegenModel fromModel(String name, Schema schema, Map<String, Schema> allDefinitions) {
CodegenModel m = super.fromModel(name, schema, allDefinitions);
m.optionalVars = m.optionalVars.stream().distinct().collect(Collectors.toList());
m.allVars.stream().filter(p -> !m.vars.contains(p)).forEach(p -> p.isInherited = true);
return m;
}
}

View File

@ -3,14 +3,23 @@
{{#vars}}
* @param {{name}} {{{description}}}
{{/vars}}
*/
data class {{classname}} (
*/{{#discriminator}}
{{>typeInfoAnnotation}}{{/discriminator}}
{{#discriminator}}interface {{classname}}{{/discriminator}}{{^discriminator}}data class {{classname}} (
{{#requiredVars}}
{{>dataClassReqVar}}{{^-last}},
{{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}},
{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>dataClassOptVar}}{{^-last}},
{{/-last}}{{/optionalVars}}
) {
) {{/discriminator}}{{#parent}}: {{{parent}}}{{/parent}}{
{{#discriminator}}
{{#requiredVars}}
{{>interfaceReqVar}}
{{/requiredVars}}
{{#optionalVars}}
{{>interfaceOptVar}}
{{/optionalVars}}
{{/discriminator}}
{{#hasEnums}}{{#vars}}{{#isEnum}}
/**
* {{{description}}}

View File

@ -1,4 +1,4 @@
{{#useBeanValidation}}{{#required}}
@get:NotNull {{/required}}{{>beanValidationModel}}{{/useBeanValidation}}{{#swaggerAnnotations}}
@ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}"){{/swaggerAnnotations}}
@JsonProperty("{{{baseName}}}") val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}
@JsonProperty("{{{baseName}}}"){{#isInherited}} override{{/isInherited}} val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}

View File

@ -1,4 +1,4 @@
{{#useBeanValidation}}{{#required}}
@get:NotNull {{/required}}{{>beanValidationModel}}{{/useBeanValidation}}{{#swaggerAnnotations}}
@ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}"){{/swaggerAnnotations}}
@JsonProperty("{{{baseName}}}") val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}
@JsonProperty("{{{baseName}}}"){{#isInherited}} override{{/isInherited}} val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}

View File

@ -0,0 +1,3 @@
{{#swaggerAnnotations}}
@ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}"){{/swaggerAnnotations}}
val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? {{^discriminator}}= {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}{{/discriminator}}

View File

@ -0,0 +1,3 @@
{{#swaggerAnnotations}}
@ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}"){{/swaggerAnnotations}}
val {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}

View File

@ -1 +1 @@
{{#useOptional}}{{#required}}{{{dataType}}}{{/required}}{{^required}}Optional<{{{dataType}}}>{{/required}}{{/useOptional}}{{^useOptional}}{{{dataType}}}{{/useOptional}}
{{#required}}{{{dataType}}}{{/required}}{{^required}}{{{dataType}}}?{{/required}}

View File

@ -0,0 +1,8 @@
{{#jackson}}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "{{{discriminatorName}}}", visible = true)
@JsonSubTypes(
{{#discriminator.mappedModels}}
JsonSubTypes.Type(value = {{modelName}}::class, name = "{{^vendorExtensions.x-discriminator-value}}{{mappingName}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}"){{^-last}},{{/-last}}
{{/discriminator.mappedModels}}
){{/jackson}}

View File

@ -56,7 +56,7 @@ class PetApiController(@Autowired(required = true) val service: PetApiService) {
@RequestMapping(
value = ["/pet/{petId}"],
method = [RequestMethod.DELETE])
fun deletePet(@ApiParam(value = "Pet id to delete", required=true) @PathVariable("petId") petId: kotlin.Long,@ApiParam(value = "" ) @RequestHeader(value="api_key", required=false) apiKey: kotlin.String): ResponseEntity<Unit> {
fun deletePet(@ApiParam(value = "Pet id to delete", required=true) @PathVariable("petId") petId: kotlin.Long,@ApiParam(value = "" ) @RequestHeader(value="api_key", required=false) apiKey: kotlin.String?): ResponseEntity<Unit> {
return ResponseEntity(service.deletePet(petId, apiKey), HttpStatus.OK)
}

View File

@ -7,7 +7,7 @@ interface PetApiService {
fun addPet(pet: Pet): Unit
fun deletePet(petId: kotlin.Long,apiKey: kotlin.String): Unit
fun deletePet(petId: kotlin.Long,apiKey: kotlin.String?): Unit
fun findPetsByStatus(status: kotlin.Array<kotlin.String>): List<Pet>
@ -17,7 +17,7 @@ interface PetApiService {
fun updatePet(pet: Pet): Unit
fun updatePetWithForm(petId: kotlin.Long,name: kotlin.String,status: kotlin.String): Unit
fun updatePetWithForm(petId: kotlin.Long,name: kotlin.String?,status: kotlin.String?): Unit
fun uploadFile(petId: kotlin.Long,additionalMetadata: kotlin.String,file: org.springframework.web.multipart.MultipartFile): ModelApiResponse
fun uploadFile(petId: kotlin.Long,additionalMetadata: kotlin.String?,file: org.springframework.web.multipart.MultipartFile): ModelApiResponse
}

View File

@ -11,7 +11,7 @@ class PetApiServiceImpl : PetApiService {
TODO("Implement me")
}
override fun deletePet(petId: kotlin.Long,apiKey: kotlin.String): Unit {
override fun deletePet(petId: kotlin.Long,apiKey: kotlin.String?): Unit {
TODO("Implement me")
}
@ -31,11 +31,11 @@ class PetApiServiceImpl : PetApiService {
TODO("Implement me")
}
override fun updatePetWithForm(petId: kotlin.Long,name: kotlin.String,status: kotlin.String): Unit {
override fun updatePetWithForm(petId: kotlin.Long,name: kotlin.String?,status: kotlin.String?): Unit {
TODO("Implement me")
}
override fun uploadFile(petId: kotlin.Long,additionalMetadata: kotlin.String,file: org.springframework.web.multipart.MultipartFile): ModelApiResponse {
override fun uploadFile(petId: kotlin.Long,additionalMetadata: kotlin.String?,file: org.springframework.web.multipart.MultipartFile): ModelApiResponse {
TODO("Implement me")
}
}

View File

@ -56,7 +56,7 @@ class PetApiController(@Autowired(required = true) val service: PetApiService) {
@RequestMapping(
value = ["/pet/{petId}"],
method = [RequestMethod.DELETE])
fun deletePet(@ApiParam(value = "Pet id to delete", required=true) @PathVariable("petId") petId: kotlin.Long,@ApiParam(value = "" ) @RequestHeader(value="api_key", required=false) apiKey: kotlin.String): ResponseEntity<Unit> {
fun deletePet(@ApiParam(value = "Pet id to delete", required=true) @PathVariable("petId") petId: kotlin.Long,@ApiParam(value = "" ) @RequestHeader(value="api_key", required=false) apiKey: kotlin.String?): ResponseEntity<Unit> {
return ResponseEntity(service.deletePet(petId, apiKey), HttpStatus.OK)
}

View File

@ -7,7 +7,7 @@ interface PetApiService {
fun addPet(pet: Pet): Unit
fun deletePet(petId: kotlin.Long,apiKey: kotlin.String): Unit
fun deletePet(petId: kotlin.Long,apiKey: kotlin.String?): Unit
fun findPetsByStatus(status: kotlin.Array<kotlin.String>): List<Pet>
@ -17,7 +17,7 @@ interface PetApiService {
fun updatePet(pet: Pet): Unit
fun updatePetWithForm(petId: kotlin.Long,name: kotlin.String,status: kotlin.String): Unit
fun updatePetWithForm(petId: kotlin.Long,name: kotlin.String?,status: kotlin.String?): Unit
fun uploadFile(petId: kotlin.Long,additionalMetadata: kotlin.String,file: org.springframework.web.multipart.MultipartFile): ModelApiResponse
fun uploadFile(petId: kotlin.Long,additionalMetadata: kotlin.String?,file: org.springframework.web.multipart.MultipartFile): ModelApiResponse
}

View File

@ -11,7 +11,7 @@ class PetApiServiceImpl : PetApiService {
TODO("Implement me")
}
override fun deletePet(petId: kotlin.Long,apiKey: kotlin.String): Unit {
override fun deletePet(petId: kotlin.Long,apiKey: kotlin.String?): Unit {
TODO("Implement me")
}
@ -31,11 +31,11 @@ class PetApiServiceImpl : PetApiService {
TODO("Implement me")
}
override fun updatePetWithForm(petId: kotlin.Long,name: kotlin.String,status: kotlin.String): Unit {
override fun updatePetWithForm(petId: kotlin.Long,name: kotlin.String?,status: kotlin.String?): Unit {
TODO("Implement me")
}
override fun uploadFile(petId: kotlin.Long,additionalMetadata: kotlin.String,file: org.springframework.web.multipart.MultipartFile): ModelApiResponse {
override fun uploadFile(petId: kotlin.Long,additionalMetadata: kotlin.String?,file: org.springframework.web.multipart.MultipartFile): ModelApiResponse {
TODO("Implement me")
}
}