Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions .github/workflows/samples-kotlin-server-jdk17.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- 'samples/server/petstore/kotlin-server-modelMutable/**'
- 'samples/server/petstore/kotlin-springboot-*/**'
- 'samples/server/petstore/kotlin-server-required-and-nullable-properties/**'
- 'samples/server/petstore/kotlin-spring-declarative*/**'
# comment out due to gradle build failure
# - samples/server/petstore/kotlin-spring-default/**
pull_request:
Expand All @@ -17,6 +18,7 @@ on:
- 'samples/server/petstore/kotlin-server-modelMutable/**'
- 'samples/server/petstore/kotlin-springboot-*/**'
- 'samples/server/petstore/kotlin-server-required-and-nullable-properties/**'
- 'samples/server/petstore/kotlin-spring-declarative*/**'
# comment out due to gradle build failure
# - samples/server/petstore/kotlin-spring-default/**

Expand Down Expand Up @@ -44,6 +46,10 @@ jobs:
- samples/server/petstore/kotlin-server/ktor
- samples/server/petstore/kotlin-server/ktor2
- samples/server/petstore/kotlin-misk
- samples/server/petstore/kotlin-spring-declarative-interface
- samples/server/petstore/kotlin-spring-declarative-interface-reactive-coroutines
- samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped
- samples/server/petstore/kotlin-spring-declarative-interface-wrapped
# comment out due to gradle build failure
# - samples/server/petstore/kotlin-spring-default/
steps:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
generatorName: kotlin-spring
outputDir: samples/server/petstore/kotlin-spring-declarative-interface-reactive-coroutines
library: spring-declarative-http-interface
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
additionalProperties:
documentationProvider: springDoc
annotationLibrary: swagger2
useSwaggerUI: "false"
serializableModel: "true"
beanValidations: "true"
interfaceOnly: true
reactive: true
declarativeInterfaceWrapResponses: false
useFlowForArrayReturnType: false
declarativeInterfaceReactiveMode: "coroutines"
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
generatorName: kotlin-spring
outputDir: samples/server/petstore/kotlin-spring-declarative-interface-reactive-reactor-wrapped
library: spring-declarative-http-interface
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
additionalProperties:
documentationProvider: springDoc
annotationLibrary: swagger2
useSwaggerUI: "false"
serializableModel: "true"
beanValidations: "true"
interfaceOnly: true
reactive: true
declarativeInterfaceWrapResponses: true
useFlowForArrayReturnType: false
declarativeInterfaceReactiveMode: "reactor"
15 changes: 15 additions & 0 deletions bin/configs/kotlin-spring-declarative-interface-wrapped.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
generatorName: kotlin-spring
outputDir: samples/server/petstore/kotlin-spring-declarative-interface-wrapped
library: spring-declarative-http-interface
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
additionalProperties:
documentationProvider: springDoc
annotationLibrary: swagger2
useSwaggerUI: "false"
serializableModel: "true"
beanValidations: "true"
interfaceOnly: true
reactive: false
declarativeInterfaceWrapResponses: true
useFlowForArrayReturnType: false
15 changes: 15 additions & 0 deletions bin/configs/kotlin-spring-declarative-interface.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
generatorName: kotlin-spring
outputDir: samples/server/petstore/kotlin-spring-declarative-interface
library: spring-declarative-http-interface
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/kotlin-spring
additionalProperties:
documentationProvider: springDoc
annotationLibrary: swagger2
useSwaggerUI: "false"
serializableModel: "true"
beanValidations: "true"
interfaceOnly: true
reactive: false
declarativeInterfaceWrapResponses: true
useFlowForArrayReturnType: false
4 changes: 3 additions & 1 deletion docs/generators/kotlin-spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|basePackage|base package (invokerPackage) for generated code| |org.openapitools|
|beanQualifiers|Whether to add fully-qualifier class names as bean qualifiers in @Component and @RestController annotations. May be used to prevent bean names clash if multiple generated libraries (contexts) added to single project.| |false|
|configPackage|configuration package for generated code| |org.openapitools.configuration|
|declarativeInterfaceReactiveMode|What type of reactive style to use in Spring Http declarative interface|<dl><dt>**coroutines**</dt><dd>Use kotlin-idiomatic 'suspend' functions</dd><dt>**reactor**</dt><dd>Use reactor return wrappers 'Mono' and 'Flux'</dd></dl>|coroutines|
|declarativeInterfaceWrapResponses|Whether (when false) to return actual type (e.g. List&lt;Fruit&gt;) and handle non 2xx responses via exceptions or (when true) return entire ResponseEntity (e.g. ResponseEntity&lt;List&lt;Fruit&gt;&gt;)| |false|
|delegatePattern|Whether to generate the server files using the delegate pattern| |false|
|documentationProvider|Select the OpenAPI documentation provider.|<dl><dt>**none**</dt><dd>Do not publish an OpenAPI specification.</dd><dt>**source**</dt><dd>Publish the original input OpenAPI specification.</dd><dt>**springfox**</dt><dd>Generate an OpenAPI 2 (fka Swagger RESTful API Documentation Specification) specification using SpringFox 2.x. Deprecated (for removal); use springdoc instead.</dd><dt>**springdoc**</dt><dd>Generate an OpenAPI 3 specification using SpringDoc.</dd></dl>|springdoc|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
|exceptionHandler|generate default global exception handlers (not compatible with reactive. enabling reactive will disable exceptionHandler )| |true|
|gradleBuildFile|generate a gradle build file using the Kotlin DSL| |true|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|interfaceOnly|Whether to generate only API interface stubs without the server files.| |false|
|library|library template (sub-template)|<dl><dt>**spring-boot**</dt><dd>Spring-boot Server application.</dd><dt>**spring-cloud**</dt><dd>Spring-Cloud-Feign client with Spring-Boot auto-configured settings.</dd></dl>|spring-boot|
|library|library template (sub-template)|<dl><dt>**spring-boot**</dt><dd>Spring-boot Server application.</dd><dt>**spring-cloud**</dt><dd>Spring-Cloud-Feign client with Spring-Boot auto-configured settings.</dd><dt>**spring-declarative-http-interface**</dt><dd>Spring Declarative Interface client</dd></dl>|spring-boot|
|modelMutable|Create mutable models| |false|
|modelPackage|model package for generated code| |org.openapitools.model|
|packageName|Generated artifact package name.| |org.openapitools|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
public static final String BASE_PACKAGE = "basePackage";
public static final String SPRING_BOOT = "spring-boot";
public static final String SPRING_CLOUD_LIBRARY = "spring-cloud";
public static final String SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY = "spring-declarative-http-interface";
public static final String EXCEPTION_HANDLER = "exceptionHandler";
public static final String GRADLE_BUILD_FILE = "gradleBuildFile";
public static final String SERVICE_INTERFACE = "serviceInterface";
Expand All @@ -84,13 +85,29 @@ public class KotlinSpringServerCodegen extends AbstractKotlinCodegen
public static final String DELEGATE_PATTERN = "delegatePattern";
public static final String USE_TAGS = "useTags";
public static final String BEAN_QUALIFIERS = "beanQualifiers";
public static final String DECLARATIVE_INTERFACE_WRAP_RESPONSES = "declarativeInterfaceWrapResponses";
public static final String DECLARATIVE_INTERFACE_REACTIVE_MODE = "declarativeInterfaceReactiveMode";

public static final String USE_SPRING_BOOT3 = "useSpringBoot3";
public static final String USE_FLOW_FOR_ARRAY_RETURN_TYPE = "useFlowForArrayReturnType";
public static final String REQUEST_MAPPING_OPTION = "requestMappingMode";
public static final String USE_REQUEST_MAPPING_ON_CONTROLLER = "useRequestMappingOnController";
public static final String USE_REQUEST_MAPPING_ON_INTERFACE = "useRequestMappingOnInterface";

@Getter
public enum DeclarativeInterfaceReactiveMode {
coroutines("Use kotlin-idiomatic 'suspend' functions", "reactiveModeCoroutines"),
reactor("Use reactor return wrappers 'Mono' and 'Flux'", "reactiveModeReactor");

private final String description;
private final String additionalPropertyName;

DeclarativeInterfaceReactiveMode(String description, String additionalPropertyName) {
this.description = description;
this.additionalPropertyName = additionalPropertyName;
}
}

public enum RequestMappingMode {
api_interface("Generate the @RequestMapping annotation on the generated Api Interface."),
controller("Generate the @RequestMapping annotation on the generated Api Controller Implementation."),
Expand Down Expand Up @@ -135,6 +152,8 @@ public String getDescription() {
@Setter private boolean delegatePattern = false;
@Setter protected boolean useTags = false;
@Setter private boolean beanQualifiers = false;
@Setter private DeclarativeInterfaceReactiveMode declarativeInterfaceReactiveMode = DeclarativeInterfaceReactiveMode.coroutines;
@Setter private boolean declarativeInterfaceWrapResponses = false;

@Getter @Setter
protected boolean useSpringBoot3 = false;
Expand Down Expand Up @@ -220,9 +239,14 @@ public KotlinSpringServerCodegen() {
" (contexts) added to single project.", beanQualifiers);
addSwitch(USE_SPRING_BOOT3, "Generate code and provide dependencies for use with Spring Boot 3.x. (Use jakarta instead of javax in imports). Enabling this option will also enable `useJakartaEe`.", useSpringBoot3);
addSwitch(USE_FLOW_FOR_ARRAY_RETURN_TYPE, "Whether to use Flow for array/collection return types when reactive is enabled. If false, will use List instead.", useFlowForArrayReturnType);
addSwitch(DECLARATIVE_INTERFACE_WRAP_RESPONSES,
"Whether (when false) to return actual type (e.g. List<Fruit>) and handle non 2xx responses via exceptions or (when true) return entire ResponseEntity (e.g. ResponseEntity<List<Fruit>>)",
declarativeInterfaceWrapResponses);
supportedLibraries.put(SPRING_BOOT, "Spring-boot Server application.");
supportedLibraries.put(SPRING_CLOUD_LIBRARY,
"Spring-Cloud-Feign client with Spring-Boot auto-configured settings.");
supportedLibraries.put(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY,
"Spring Declarative Interface client");
setLibrary(SPRING_BOOT);

CliOption cliOpt = new CliOption(CodegenConstants.LIBRARY, CodegenConstants.LIBRARY_DESC);
Expand All @@ -238,6 +262,14 @@ public KotlinSpringServerCodegen() {
}
cliOptions.add(requestMappingOpt);

CliOption declarativeInterfaceReactiveModeOpt = new CliOption(DECLARATIVE_INTERFACE_REACTIVE_MODE,
"What type of reactive style to use in Spring Http declarative interface")
.defaultValue(declarativeInterfaceReactiveMode.name());
for (DeclarativeInterfaceReactiveMode mode : DeclarativeInterfaceReactiveMode.values()) {
declarativeInterfaceReactiveModeOpt.addEnum(mode.name(), mode.getDescription());
}
cliOptions.add(declarativeInterfaceReactiveModeOpt);

if (null != defaultDocumentationProvider()) {
CliOption documentationProviderCliOption = new CliOption(DOCUMENTATION_PROVIDER,
"Select the OpenAPI documentation provider.")
Expand Down Expand Up @@ -518,6 +550,41 @@ public void processOpts() {
this.setUseFlowForArrayReturnType(convertPropertyToBoolean(USE_FLOW_FOR_ARRAY_RETURN_TYPE));
}
}
if (library.equals(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY)) {
this.setReactive(convertPropertyToBoolean(REACTIVE));
if (additionalProperties.containsKey(USE_FLOW_FOR_ARRAY_RETURN_TYPE)) {
this.setUseFlowForArrayReturnType(convertPropertyToBoolean(USE_FLOW_FOR_ARRAY_RETURN_TYPE));
}
if (this.isUseFlowForArrayReturnType()) {
{
throw new IllegalArgumentException("Additional property '" + USE_FLOW_FOR_ARRAY_RETURN_TYPE + "' must be set to 'false' as it is not supported by Spring declarative HTTP interface");
}
}
if (additionalProperties.containsKey(DECLARATIVE_INTERFACE_REACTIVE_MODE)) {
try {
DeclarativeInterfaceReactiveMode optValue = DeclarativeInterfaceReactiveMode.valueOf(
String.valueOf(additionalProperties.get(DECLARATIVE_INTERFACE_REACTIVE_MODE)));
setDeclarativeInterfaceReactiveMode(optValue);
writePropertyBack(optValue.getAdditionalPropertyName(), true);
additionalProperties.remove(DECLARATIVE_INTERFACE_REACTIVE_MODE);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
"Invalid value for additional property '" + DECLARATIVE_INTERFACE_REACTIVE_MODE + "'. Supported values are " + Arrays.toString(DeclarativeInterfaceReactiveMode.values()) + "."
);
}
}
}
}
if (SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY.equals(library)) {
this.setUseSpringBoot3(true);
this.setInterfaceOnly(true);
this.setUseFeignClient(false);
this.setSkipDefaultInterface(true);

writePropertyBack(USE_SPRING_BOOT3, useSpringBoot3);
writePropertyBack(INTERFACE_ONLY, interfaceOnly);
writePropertyBack(USE_FEIGN_CLIENT, useFeignClient);
writePropertyBack(SKIP_DEFAULT_INTERFACE, skipDefaultInterface);
}
writePropertyBack(REACTIVE, reactive);
writePropertyBack(EXCEPTION_HANDLER, exceptionHandler);
Expand Down Expand Up @@ -606,7 +673,7 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));


if (this.exceptionHandler && !library.equals(SPRING_CLOUD_LIBRARY)) {
if (this.exceptionHandler && !(library.equals(SPRING_CLOUD_LIBRARY) || library.equals(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY))) {
supportingFiles.add(new SupportingFile("exceptions.mustache",
sanitizeDirectory(sourceFolder + File.separator + apiPackage), "Exceptions.kt"));
}
Expand Down Expand Up @@ -699,8 +766,29 @@ public void processOpts() {

apiTestTemplateFiles.clear();
}
if (library.equals(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY)) {
LOGGER.info("Setup code generator for Kotlin Spring Declarative Http interface");

supportingFiles.add(new SupportingFile("pom-sb3.mustache", "pom.xml"));

if (this.gradleBuildFile) {
supportingFiles.add(new SupportingFile("buildGradle-sb3-Kts.mustache", "build.gradle.kts"));
supportingFiles.add(new SupportingFile("settingsGradle.mustache", "settings.gradle"));

String gradleWrapperPackage = "gradle.wrapper";
supportingFiles.add(new SupportingFile("gradlew.mustache", "", "gradlew"));
supportingFiles.add(new SupportingFile("gradlew.bat.mustache", "", "gradlew.bat"));
supportingFiles.add(new SupportingFile("gradle-wrapper.properties.mustache",
gradleWrapperPackage.replace(".", File.separator), "gradle-wrapper.properties"));
supportingFiles.add(new SupportingFile("gradle-wrapper.jar",
gradleWrapperPackage.replace(".", File.separator), "gradle-wrapper.jar"));
}

apiTemplateFiles.put("apiInterface.mustache", "Client.kt");
apiTestTemplateFiles.clear();
}

if (!reactive && !library.equals(SPRING_CLOUD_LIBRARY)) {
if (!reactive && !(library.equals(SPRING_CLOUD_LIBRARY) || library.equals(SPRING_DECLARATIVE_HTTP_INTERFACE_LIBRARY))) {
if (DocumentationProvider.SPRINGFOX.equals(getDocumentationProvider())) {
supportingFiles.add(new SupportingFile("springfoxDocumentationConfig.mustache",
(sourceFolder + File.separator + basePackage).replace(".", java.io.File.separator),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# OpenAPI generated API stub

Spring Framework stub

## Overview
This code was generated by the [OpenAPI Generator](https://openapi-generator.tech) project.
By using the [OpenAPI-Spec](https://openapis.org).
This generates Spring 6+ declarative HTTP interfaces that can be used to easily instantiate an http client.
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* NOTE: Auto generated by OpenAPI Generator ({{{generatorVersion}}})
* Spring 6 Declarative HTTP Interface
*/
package {{package}}

{{#imports}}import {{import}}
{{/imports}}
{{#useRequestMappingOnInterface}}
import {{#apiPackage}}{{.}}.{{/apiPackage}}{{classname}}.Companion.BASE_PATH
{{/useRequestMappingOnInterface}}

{{#swagger2AnnotationLibrary}}
import io.swagger.v3.oas.annotations.*
import io.swagger.v3.oas.annotations.enums.*
import io.swagger.v3.oas.annotations.media.*
import io.swagger.v3.oas.annotations.responses.*
import io.swagger.v3.oas.annotations.security.*
{{/swagger2AnnotationLibrary}}
{{#swagger1AnnotationLibrary}}
import io.swagger.annotations.Api
import io.swagger.annotations.ApiOperation
import io.swagger.annotations.ApiParam
import io.swagger.annotations.ApiResponse
import io.swagger.annotations.ApiResponses
import io.swagger.annotations.Authorization
import io.swagger.annotations.AuthorizationScope
{{/swagger1AnnotationLibrary}}

import org.springframework.web.service.annotation.*
import org.springframework.web.bind.annotation.*
import org.springframework.http.ResponseEntity

{{#useBeanValidation}}
import org.springframework.validation.annotation.Validated
import {{javaxPackage}}.validation.Valid
import {{javaxPackage}}.validation.constraints.*
{{/useBeanValidation}}

{{#reactiveModeReactor}}
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
{{/reactiveModeReactor}}

import kotlin.collections.List
import kotlin.collections.Map

{{#useRequestMappingOnInterface}}@HttpExchange(
"{{=<% %>=}}\${api.base-path:$BASE_PATH}<%={{ }}=%>"
){{/useRequestMappingOnInterface}}
{{#useBeanValidation}}
@Validated
{{/useBeanValidation}}
{{#operations}}
interface {{classname}} {

{{#operation}}
{{#httpMethod}}
@HttpExchange(
url = PATH_{{#lambda.uppercase}}{{#lambda.snakecase}}{{{operationId}}}{{/lambda.snakecase}}{{/lambda.uppercase}},
method = "{{httpMethod}}"
)
{{/httpMethod}}{{!
}}{{#reactiveModeCoroutines}}suspend {{/reactiveModeCoroutines}}{{!
}}fun {{operationId}}(
{{#allParams}}
{{>queryParams}}{{>pathParams}}{{>headerParams}}{{>cookieParams}}{{>httpInterfaceBodyParams}}{{>formParams}}{{^-last}},{{/-last}}
{{/allParams}}
): {{>httpInterfaceReturnTypes}}

{{/operation}}
companion object {
//for your own safety never directly reuse these path definitions in tests
{{#useRequestMappingOnInterface}}
const val BASE_PATH: String = "{{=<% %>=}}<%contextPath%><%={{ }}=%>"
{{/useRequestMappingOnInterface}}
{{#operation}}
const val PATH_{{#lambda.uppercase}}{{#lambda.snakecase}}{{{operationId}}}{{/lambda.snakecase}}{{/lambda.uppercase}}: String = "{{{path}}}"
{{/operation}}
}
}
{{/operations}}
Loading
Loading