Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 5497: Support the use of tags in the delegated Spring Kotlin generator. #5499

Merged
merged 2 commits into from
Jun 7, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/generators/kotlin-spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ sidebar_label: kotlin-spring
|swaggerAnnotations|generate swagger annotations to go alongside controllers and models| |false|
|title|server title name or client service name| |OpenAPI Kotlin Spring|
|useBeanValidation|Use BeanValidation API annotations to validate data types| |true|
|useTags|Whether to use tags for creating interface and controller class names| |false|

## IMPORT MAPPING

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
public static final String REACTIVE = "reactive";
public static final String INTERFACE_ONLY = "interfaceOnly";
public static final String DELEGATE_PATTERN = "delegatePattern";
public static final String USE_TAGS = "useTags";

private String basePackage;
private String invokerPackage;
Expand All @@ -81,6 +82,7 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
private boolean reactive = false;
private boolean interfaceOnly = false;
private boolean delegatePattern = false;
protected boolean useTags = false;

public KotlinSpringServerCodegen() {
super();
Expand Down Expand Up @@ -152,6 +154,7 @@ public KotlinSpringServerCodegen() {
addSwitch(REACTIVE, "use coroutines for reactive behavior", reactive);
addSwitch(INTERFACE_ONLY, "Whether to generate only API interface stubs without the server files.", interfaceOnly);
addSwitch(DELEGATE_PATTERN, "Whether to generate the server files using the delegate pattern", delegatePattern);
addSwitch(USE_TAGS, "Whether to use tags for creating interface and controller class names", useTags);
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
setLibrary(SPRING_BOOT);

Expand Down Expand Up @@ -245,6 +248,10 @@ public void setDelegatePattern(boolean delegatePattern) {
this.delegatePattern = delegatePattern;
}

public void setUseTags(boolean useTags) {
this.useTags = useTags;
}

@Override
public void setUseBeanValidation(boolean useBeanValidation) {
this.useBeanValidation = useBeanValidation;
Expand Down Expand Up @@ -369,6 +376,10 @@ public void processOpts() {
}
}

if (additionalProperties.containsKey(USE_TAGS)) {
this.setUseTags(Boolean.parseBoolean(additionalProperties.get(USE_TAGS).toString()));
}

modelTemplateFiles.put("model.mustache", ".kt");

if (!this.interfaceOnly && this.delegatePattern) {
Expand Down Expand Up @@ -440,7 +451,7 @@ protected Builder<String, Lambda> addMustacheLambdas() {

@Override
public void addOperationToGroup(String tag, String resourcePath, Operation operation, CodegenOperation co, Map<String, List<CodegenOperation>> operations) {
if (library.equals(SPRING_BOOT) && this.delegatePattern) {
if (library.equals(SPRING_BOOT) && !useTags) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes previous behavior for library.equals(SPRING_BOOT) && this.delegatePattern and would be a breaking change. Was this intentional? If so, can you speak toward the intention? To me it looks like this.delegatePattern was accidentally removed.

Copy link
Contributor Author

@dumitrupetrusca-okta dumitrupetrusca-okta Jun 5, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment Jim. The replacement of this.delegatePattern with useTags was intentional. I'm currently using the delegate pattern with tags in my Java project and this change was needed to get the Kotlin generator to produce equivalent code to the Java one. In fact if you look at SpringCodegen.java you can see it does something very similar

if ((library.equals(SPRING_BOOT) || library.equals(SPRING_MVC_LIBRARY)) && !useTags) {

I understand that this is a breaking change but I'm not sure we can support tags properly while keeping backwards compatibility.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I'll mark this PR as a breaking change. Thanks for the explanation.

String basePath = resourcePath;
if (basePath.startsWith("/")) {
basePath = basePath.substring(1);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.openapitools.codegen.kotlin.spring;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.testing.Helpers;
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
Expand All @@ -21,6 +22,7 @@
import java.io.File;
import java.nio.file.Files;
import java.util.Collections;
import java.util.List;

public class KotlinSpringServerCodegenTest {

Expand Down Expand Up @@ -183,4 +185,28 @@ public void testDelegatePattern() {

Assert.assertTrue(codegen.supportingFiles().stream().anyMatch(supportingFile -> supportingFile.templateFile.equals("apiUtil.mustache")));
}

@Test(description = "test delegate with tags")
public void delegateWithTags() throws Exception {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); //may be move to /build
KotlinSpringServerCodegen codegen = new KotlinSpringServerCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(KotlinSpringServerCodegen.DELEGATE_PATTERN, true);
codegen.additionalProperties().put(KotlinSpringServerCodegen.USE_TAGS, true);

List<File> files = new DefaultGenerator()
.opts(
new ClientOptInput()
.openAPI(TestUtils.parseSpec("src/test/resources/3_0/kotlin/issue5497-use-tags-kotlin.yaml"))
.config(codegen)
)
.generate();

Helpers.assertContainsAllOf(files,
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiController.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV1ApiDelegate.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV2ApiController.kt"),
new File(output, "src/main/kotlin/org/openapitools/api/TestV2ApiDelegate.kt")
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
openapi: "3.0.1"
info:
title: test
version: "1.0"

paths:

/api/v1/test:
get:
tags: [Test v1]
operationId: test1
responses:
200:
description: OK

/api/v2/test:
get:
tags: [Test v2]
operationId: test2
responses:
200:
description: OK
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.2.3-SNAPSHOT
4.3.0-SNAPSHOT
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript {
repositories {
jcenter()
mavenCentral()
maven { url = uri("https://repo1.maven.org/maven2") }
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.2.0.M3")
Expand All @@ -15,7 +15,7 @@ version = "1.0.0"

repositories {
jcenter()
mavenCentral()
maven { url = uri("https://repo1.maven.org/maven2") }
}

tasks.withType<KotlinCompile> {
Expand Down Expand Up @@ -48,7 +48,7 @@ dependencies {
}

repositories {
mavenCentral()
maven { url = uri("https://repo1.maven.org/maven2") }
maven { url = uri("https://repo.spring.io/snapshot") }
maven { url = uri("https://repo.spring.io/milestone") }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.openapitools.api

import org.springframework.web.context.request.NativeWebRequest

import javax.servlet.http.HttpServletResponse
import java.io.IOException

object ApiUtil {
fun setExampleResponse(req: NativeWebRequest, contentType: String, example: String) {
try {
val res = req.getNativeResponse(HttpServletResponse::class.java)
res.setCharacterEncoding("UTF-8")
res.addHeader("Content-Type", contentType)
res.getWriter().print(example)
} catch (e: IOException) {
throw RuntimeException(e)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ class DefaultExceptionHandler {

@ExceptionHandler(value = [ApiException::class])
fun onApiException(ex: ApiException, response: HttpServletResponse): Unit =
response.sendError(ex.code, ex.message)
response.sendError(ex.code, ex.message)

@ExceptionHandler(value = [NotImplementedError::class])
fun onNotImplemented(ex: NotImplementedError, response: HttpServletResponse): Unit =
response.sendError(HttpStatus.NOT_IMPLEMENTED.value())
response.sendError(HttpStatus.NOT_IMPLEMENTED.value())

@ExceptionHandler(value = [ConstraintViolationException::class])
fun onConstraintViolation(ex: ConstraintViolationException, response: HttpServletResponse): Unit =
response.sendError(HttpStatus.BAD_REQUEST.value(), ex.constraintViolations.joinToString(", ") { it.message })
response.sendError(HttpStatus.BAD_REQUEST.value(), ex.constraintViolations.joinToString(", ") { it.message })
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import kotlin.collections.Map

@RestController
@Validated
@Api(value = "Pet", description = "The Pet API")
@Api(value = "pet", description = "The pet API")
@RequestMapping("\${api.base-path:/v2}")
class PetApiController(@Autowired(required = true) val service: PetApiService) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import kotlin.collections.Map

@RestController
@Validated
@Api(value = "Store", description = "The Store API")
@Api(value = "store", description = "The store API")
@RequestMapping("\${api.base-path:/v2}")
class StoreApiController(@Autowired(required = true) val service: StoreApiService) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import kotlin.collections.Map

@RestController
@Validated
@Api(value = "User", description = "The User API")
@Api(value = "user", description = "The user API")
@RequestMapping("\${api.base-path:/v2}")
class UserApiController(@Autowired(required = true) val service: UserApiService) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import io.swagger.annotations.ApiModelProperty
* @param id
* @param name
*/
data class Category (
data class Category(

@ApiModelProperty(example = "null", value = "")
@JsonProperty("id") var id: kotlin.Long? = null,
@ApiModelProperty(example = "null", value = "")
@JsonProperty("id") var id: kotlin.Long? = null,

@ApiModelProperty(example = "null", value = "")
@JsonProperty("name") var name: kotlin.String? = null
@ApiModelProperty(example = "null", value = "")
@JsonProperty("name") var name: kotlin.String? = null
) {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ import io.swagger.annotations.ApiModelProperty
* @param type
* @param message
*/
data class ModelApiResponse (
data class ModelApiResponse(

@ApiModelProperty(example = "null", value = "")
@JsonProperty("code") var code: kotlin.Int? = null,
@ApiModelProperty(example = "null", value = "")
@JsonProperty("code") var code: kotlin.Int? = null,

@ApiModelProperty(example = "null", value = "")
@JsonProperty("type") var type: kotlin.String? = null,
@ApiModelProperty(example = "null", value = "")
@JsonProperty("type") var type: kotlin.String? = null,

@ApiModelProperty(example = "null", value = "")
@JsonProperty("message") var message: kotlin.String? = null
@ApiModelProperty(example = "null", value = "")
@JsonProperty("message") var message: kotlin.String? = null
) {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,25 @@ import io.swagger.annotations.ApiModelProperty
* @param status Order Status
* @param complete
*/
data class Order (
data class Order(

@ApiModelProperty(example = "null", value = "")
@JsonProperty("id") var id: kotlin.Long? = null,
@ApiModelProperty(example = "null", value = "")
@JsonProperty("id") var id: kotlin.Long? = null,

@ApiModelProperty(example = "null", value = "")
@JsonProperty("petId") var petId: kotlin.Long? = null,
@ApiModelProperty(example = "null", value = "")
@JsonProperty("petId") var petId: kotlin.Long? = null,

@ApiModelProperty(example = "null", value = "")
@JsonProperty("quantity") var quantity: kotlin.Int? = null,
@ApiModelProperty(example = "null", value = "")
@JsonProperty("quantity") var quantity: kotlin.Int? = null,

@ApiModelProperty(example = "null", value = "")
@JsonProperty("shipDate") var shipDate: java.time.OffsetDateTime? = null,
@ApiModelProperty(example = "null", value = "")
@JsonProperty("shipDate") var shipDate: java.time.OffsetDateTime? = null,

@ApiModelProperty(example = "null", value = "Order Status")
@JsonProperty("status") var status: Order.Status? = null,
@ApiModelProperty(example = "null", value = "Order Status")
@JsonProperty("status") var status: Order.Status? = null,

@ApiModelProperty(example = "null", value = "")
@JsonProperty("complete") var complete: kotlin.Boolean? = null
@ApiModelProperty(example = "null", value = "")
@JsonProperty("complete") var complete: kotlin.Boolean? = null
) {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,27 @@ import io.swagger.annotations.ApiModelProperty
* @param tags
* @param status pet status in the store
*/
data class Pet (
data class Pet(

@get:NotNull
@ApiModelProperty(example = "doggie", required = true, value = "")
@JsonProperty("name") var name: kotlin.String,
@get:NotNull
@ApiModelProperty(example = "doggie", required = true, value = "")
@JsonProperty("name") var name: kotlin.String,

@get:NotNull
@ApiModelProperty(example = "null", required = true, value = "")
@JsonProperty("photoUrls") var photoUrls: kotlin.collections.List<kotlin.String>,
@get:NotNull
@ApiModelProperty(example = "null", required = true, value = "")
@JsonProperty("photoUrls") var photoUrls: kotlin.collections.List<kotlin.String>,

@ApiModelProperty(example = "null", value = "")
@JsonProperty("id") var id: kotlin.Long? = null,
@ApiModelProperty(example = "null", value = "")
@JsonProperty("id") var id: kotlin.Long? = null,

@ApiModelProperty(example = "null", value = "")
@JsonProperty("category") var category: Category? = null,
@ApiModelProperty(example = "null", value = "")
@JsonProperty("category") var category: Category? = null,

@ApiModelProperty(example = "null", value = "")
@JsonProperty("tags") var tags: kotlin.collections.List<Tag>? = null,
@ApiModelProperty(example = "null", value = "")
@JsonProperty("tags") var tags: kotlin.collections.List<Tag>? = null,

@ApiModelProperty(example = "null", value = "pet status in the store")
@JsonProperty("status") var status: Pet.Status? = null
@ApiModelProperty(example = "null", value = "pet status in the store")
@JsonProperty("status") var status: Pet.Status? = null
) {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import io.swagger.annotations.ApiModelProperty
* @param id
* @param name
*/
data class Tag (
data class Tag(

@ApiModelProperty(example = "null", value = "")
@JsonProperty("id") var id: kotlin.Long? = null,
@ApiModelProperty(example = "null", value = "")
@JsonProperty("id") var id: kotlin.Long? = null,

@ApiModelProperty(example = "null", value = "")
@JsonProperty("name") var name: kotlin.String? = null
@ApiModelProperty(example = "null", value = "")
@JsonProperty("name") var name: kotlin.String? = null
) {

}
Expand Down
Loading