Skip to content

Commit d0fb3d4

Browse files
committed
feat(kotlin-client): add Spring Boot 4 support for jvm-spring-restclient
Add useSpringBoot4 option that auto-enables Jackson 3 and generates RestClient code using JacksonJsonHttpMessageConverter with Spring Boot 4.
1 parent 69d1430 commit d0fb3d4

49 files changed

Lines changed: 3762 additions & 2 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
generatorName: kotlin
2+
outputDir: samples/client/petstore/kotlin-jvm-spring-4-restclient-jackson3
3+
library: jvm-spring-restclient
4+
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
5+
templateDir: modules/openapi-generator/src/main/resources/kotlin-client
6+
additionalProperties:
7+
artifactId: kotlin-petstore-spring4-restclient-jackson3
8+
enumUnknownDefaultCase: "true"
9+
serializationLibrary: jackson
10+
useSpringBoot4: "true"

docs/generators/kotlin.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
5656
|useRxJava3|Whether to use the RxJava3 adapter with the retrofit2 library.| |false|
5757
|useSettingsGradle|Whether the project uses settings.gradle.| |false|
5858
|useSpringBoot3|Whether to use the Spring Boot 3 with the jvm-spring-webclient library.| |false|
59+
|useSpringBoot4|Use Spring Boot 4 with the jvm-spring-restclient or jvm-spring-webclient library. Implies useJackson3.| |false|
5960

6061
## SUPPORTED VENDOR EXTENSIONS
6162

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
8888
public static final String USE_SETTINGS_GRADLE = "useSettingsGradle";
8989
public static final String IDEA = "idea";
9090
public static final String USE_SPRING_BOOT3 = "useSpringBoot3";
91+
public static final String USE_SPRING_BOOT4 = "useSpringBoot4";
9192
public static final String USE_RESPONSE_AS_RETURN_TYPE = "useResponseAsReturnType";
9293

9394
public static final String DATE_LIBRARY = "dateLibrary";
@@ -271,6 +272,7 @@ public KotlinClientCodegen() {
271272
cliOptions.add(CliOption.newBoolean(USE_RX_JAVA3, "Whether to use the RxJava3 adapter with the retrofit2 library."));
272273
cliOptions.add(CliOption.newBoolean(USE_COROUTINES, "Whether to use the Coroutines adapter with the retrofit2 library."));
273274
cliOptions.add(CliOption.newBoolean(USE_SPRING_BOOT3, "Whether to use the Spring Boot 3 with the jvm-spring-webclient library."));
275+
cliOptions.add(CliOption.newBoolean(USE_SPRING_BOOT4, "Use Spring Boot 4 with the jvm-spring-restclient or jvm-spring-webclient library. Implies useJackson3."));
274276
cliOptions.add(CliOption.newBoolean(OMIT_GRADLE_PLUGIN_VERSIONS, "Whether to declare Gradle plugin versions in build files."));
275277
cliOptions.add(CliOption.newBoolean(OMIT_GRADLE_WRAPPER, "Whether to omit Gradle wrapper for creating a sub project."));
276278
cliOptions.add(CliOption.newBoolean(USE_SETTINGS_GRADLE, "Whether the project uses settings.gradle."));
@@ -469,6 +471,12 @@ public void processOpts() {
469471
convertPropertyToBooleanAndWriteBack(USE_SPRING_BOOT3);
470472
}
471473

474+
if (additionalProperties.containsKey(USE_SPRING_BOOT4)) {
475+
convertPropertyToBooleanAndWriteBack(USE_SPRING_BOOT4);
476+
additionalProperties.put(USE_JACKSON_3, "true");
477+
setUseJackson3(true);
478+
}
479+
472480
if (additionalProperties.containsKey(CodegenConstants.SERIALIZATION_LIBRARY)) {
473481
setSerializationLibrary((String) additionalProperties.get(CodegenConstants.SERIALIZATION_LIBRARY));
474482
additionalProperties.put(this.serializationLibrary.name(), true);
@@ -869,8 +877,9 @@ private void processJvmSpringWebClientLibrary(final String infrastructureFolder)
869877
}
870878

871879
private void processJvmSpringRestClientLibrary(final String infrastructureFolder) {
872-
if (additionalProperties.getOrDefault(USE_SPRING_BOOT3, false).equals(false)) {
873-
throw new RuntimeException("This library must use Spring Boot 3. Try adding '--additional-properties useSpringBoot3=true' to your command.");
880+
if (additionalProperties.getOrDefault(USE_SPRING_BOOT3, false).equals(false)
881+
&& additionalProperties.getOrDefault(USE_SPRING_BOOT4, false).equals(false)) {
882+
throw new RuntimeException("This library requires Spring Boot 3 or 4. Try adding '--additional-properties useSpringBoot3=true' or '--additional-properties useSpringBoot4=true' to your command.");
874883
}
875884

876885
processJvmSpring(infrastructureFolder);

modules/openapi-generator/src/main/resources/kotlin-client/build.gradle.mustache

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,17 @@ buildscript {
2323
ext.vertx_version = "5.0.4"
2424
{{/jvm-vertx}}
2525
{{#jvm-spring}}
26+
{{#useSpringBoot4}}
27+
ext.spring_boot_version = "4.0.1"
28+
{{/useSpringBoot4}}
29+
{{^useSpringBoot4}}
2630
{{#useSpringBoot3}}
2731
ext.spring_boot_version = "3.5.5"
2832
{{/useSpringBoot3}}
2933
{{^useSpringBoot3}}
3034
ext.spring_boot_version = "2.7.18"
3135
{{/useSpringBoot3}}
36+
{{/useSpringBoot4}}
3237
{{/jvm-spring}}
3338
ext.spotless_version = "7.2.1"
3439

modules/openapi-generator/src/main/resources/kotlin-client/libraries/jvm-spring-restclient/api.mustache

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ import org.springframework.web.client.RestClient
99
import org.springframework.web.client.RestClientResponseException
1010

1111
{{#jackson}}
12+
{{^useJackson3}}
1213
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
14+
{{/useJackson3}}
15+
{{#useJackson3}}
16+
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter
17+
import {{packageName}}.infrastructure.Serializer
18+
{{/useJackson3}}
1319
{{/jackson}}
1420
import org.springframework.http.ResponseEntity
1521
import org.springframework.http.MediaType
@@ -23,11 +29,20 @@ import {{packageName}}.infrastructure.*
2329
{{#nonPublicApi}}internal {{/nonPublicApi}}{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}open {{/nonPublicApi}}class {{classname}}(client: RestClient) : ApiClient(client) {
2430
2531
{{#jackson}}
32+
{{^useJackson3}}
2633
{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}constructor(baseUrl: String) : this(RestClient.builder()
2734
.baseUrl(baseUrl)
2835
.messageConverters { it.add(MappingJackson2HttpMessageConverter()) }
2936
.build()
3037
)
38+
{{/useJackson3}}
39+
{{#useJackson3}}
40+
{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}constructor(baseUrl: String) : this(RestClient.builder()
41+
.baseUrl(baseUrl)
42+
.configureMessageConverters { it.withJsonConverter(JacksonJsonHttpMessageConverter(Serializer.jacksonObjectMapper)) }
43+
.build()
44+
)
45+
{{/useJackson3}}
3146
{{/jackson}}
3247

3348
{{#operation}}

modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,87 @@ public void shouldGenerateBuildGradleWithJackson3Deps() throws IOException {
10331033
TestUtils.assertFileNotContains(buildGradlePath, "com.fasterxml.jackson");
10341034
}
10351035

1036+
@Test
1037+
public void shouldAutoEnableJackson3WithSpringBoot4() throws IOException {
1038+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1039+
output.deleteOnExit();
1040+
1041+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/petstore.yaml");
1042+
final KotlinClientCodegen codegen = new KotlinClientCodegen();
1043+
codegen.setOpenAPI(openAPI);
1044+
codegen.setOutputDir(output.getAbsolutePath());
1045+
codegen.setLibrary("jvm-spring-restclient");
1046+
1047+
codegen.additionalProperties().put(KotlinClientCodegen.USE_SPRING_BOOT4, "true");
1048+
codegen.additionalProperties().put(CodegenConstants.SERIALIZATION_LIBRARY, "jackson");
1049+
1050+
ClientOptInput input = new ClientOptInput();
1051+
input.openAPI(openAPI);
1052+
input.config(codegen);
1053+
1054+
DefaultGenerator generator = new DefaultGenerator();
1055+
generator.setGenerateMetadata(false);
1056+
generator.opts(input).generate();
1057+
1058+
Assert.assertTrue(codegen.isUseJackson3(), "useSpringBoot4 should auto-enable useJackson3");
1059+
}
1060+
1061+
@Test
1062+
public void shouldGenerateSpringBoot4RestClientWithJackson3Converter() throws IOException {
1063+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1064+
output.deleteOnExit();
1065+
String outputPath = output.getAbsolutePath().replace('\\', '/');
1066+
1067+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/petstore.yaml");
1068+
final KotlinClientCodegen codegen = new KotlinClientCodegen();
1069+
codegen.setOpenAPI(openAPI);
1070+
codegen.setOutputDir(output.getAbsolutePath());
1071+
codegen.setLibrary("jvm-spring-restclient");
1072+
1073+
codegen.additionalProperties().put(KotlinClientCodegen.USE_SPRING_BOOT4, "true");
1074+
codegen.additionalProperties().put(CodegenConstants.SERIALIZATION_LIBRARY, "jackson");
1075+
1076+
ClientOptInput input = new ClientOptInput();
1077+
input.openAPI(openAPI);
1078+
input.config(codegen);
1079+
1080+
DefaultGenerator generator = new DefaultGenerator();
1081+
generator.setGenerateMetadata(false);
1082+
generator.opts(input).generate();
1083+
1084+
Path apiPath = Paths.get(outputPath + "/src/main/kotlin/org/openapitools/client/apis/PetApi.kt");
1085+
TestUtils.assertFileContains(apiPath, "JacksonJsonHttpMessageConverter");
1086+
TestUtils.assertFileNotContains(apiPath, "MappingJackson2HttpMessageConverter");
1087+
}
1088+
1089+
@Test
1090+
public void shouldGenerateBuildGradleWithSpringBoot4Deps() throws IOException {
1091+
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
1092+
output.deleteOnExit();
1093+
String outputPath = output.getAbsolutePath().replace('\\', '/');
1094+
1095+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/2_0/petstore.yaml");
1096+
final KotlinClientCodegen codegen = new KotlinClientCodegen();
1097+
codegen.setOpenAPI(openAPI);
1098+
codegen.setOutputDir(output.getAbsolutePath());
1099+
codegen.setLibrary("jvm-spring-restclient");
1100+
1101+
codegen.additionalProperties().put(KotlinClientCodegen.USE_SPRING_BOOT4, "true");
1102+
codegen.additionalProperties().put(CodegenConstants.SERIALIZATION_LIBRARY, "jackson");
1103+
1104+
ClientOptInput input = new ClientOptInput();
1105+
input.openAPI(openAPI);
1106+
input.config(codegen);
1107+
1108+
DefaultGenerator generator = new DefaultGenerator();
1109+
generator.setGenerateMetadata(false);
1110+
generator.opts(input).generate();
1111+
1112+
Path buildGradlePath = Paths.get(outputPath + "/build.gradle");
1113+
TestUtils.assertFileContains(buildGradlePath, "spring_boot_version = \"4.0.1\"");
1114+
TestUtils.assertFileNotContains(buildGradlePath, "spring_boot_version = \"3.5.5\"");
1115+
}
1116+
10361117
private static class ModelNameTest {
10371118
private final String expectedName;
10381119
private final String expectedClassName;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# OpenAPI Generator Ignore
2+
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
3+
4+
# Use this file to prevent files from being overwritten by the generator.
5+
# The patterns follow closely to .gitignore or .dockerignore.
6+
7+
# As an example, the C# client generator defines ApiClient.cs.
8+
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
9+
#ApiClient.cs
10+
11+
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
12+
#foo/*/qux
13+
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14+
15+
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16+
#foo/**/qux
17+
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18+
19+
# You can also negate patterns with an exclamation (!).
20+
# For example, you can ignore all files in a docs folder with the file extension .md:
21+
#docs/*.md
22+
# Then explicitly reverse the ignore rule for a single file:
23+
#!docs/README.md
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
.openapi-generator-ignore
2+
README.md
3+
build.gradle
4+
docs/ApiResponse.md
5+
docs/Category.md
6+
docs/Order.md
7+
docs/Pet.md
8+
docs/PetApi.md
9+
docs/StoreApi.md
10+
docs/Tag.md
11+
docs/User.md
12+
docs/UserApi.md
13+
gradle/wrapper/gradle-wrapper.jar
14+
gradle/wrapper/gradle-wrapper.properties
15+
gradlew
16+
gradlew.bat
17+
settings.gradle
18+
src/main/kotlin/org/openapitools/client/apis/PetApi.kt
19+
src/main/kotlin/org/openapitools/client/apis/StoreApi.kt
20+
src/main/kotlin/org/openapitools/client/apis/UserApi.kt
21+
src/main/kotlin/org/openapitools/client/infrastructure/ApiAbstractions.kt
22+
src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt
23+
src/main/kotlin/org/openapitools/client/infrastructure/PartConfig.kt
24+
src/main/kotlin/org/openapitools/client/infrastructure/RequestConfig.kt
25+
src/main/kotlin/org/openapitools/client/infrastructure/RequestMethod.kt
26+
src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt
27+
src/main/kotlin/org/openapitools/client/models/Category.kt
28+
src/main/kotlin/org/openapitools/client/models/ModelApiResponse.kt
29+
src/main/kotlin/org/openapitools/client/models/Order.kt
30+
src/main/kotlin/org/openapitools/client/models/Pet.kt
31+
src/main/kotlin/org/openapitools/client/models/Tag.kt
32+
src/main/kotlin/org/openapitools/client/models/User.kt
33+
src/test/kotlin/org/openapitools/client/apis/PetApiTest.kt
34+
src/test/kotlin/org/openapitools/client/apis/StoreApiTest.kt
35+
src/test/kotlin/org/openapitools/client/apis/UserApiTest.kt
36+
src/test/kotlin/org/openapitools/client/models/ApiResponseTest.kt
37+
src/test/kotlin/org/openapitools/client/models/CategoryTest.kt
38+
src/test/kotlin/org/openapitools/client/models/OrderTest.kt
39+
src/test/kotlin/org/openapitools/client/models/PetTest.kt
40+
src/test/kotlin/org/openapitools/client/models/TagTest.kt
41+
src/test/kotlin/org/openapitools/client/models/UserTest.kt
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7.21.0-SNAPSHOT
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# org.openapitools.client - Kotlin client library for OpenAPI Petstore
2+
3+
This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
4+
5+
## Overview
6+
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate an API client.
7+
8+
- API version: 1.0.0
9+
- Package version:
10+
- Generator version: 7.21.0-SNAPSHOT
11+
- Build package: org.openapitools.codegen.languages.KotlinClientCodegen
12+
13+
## Requires
14+
15+
* Kotlin 2.2.20
16+
* Gradle 8.14
17+
18+
## Build
19+
20+
First, create the gradle wrapper script:
21+
22+
```
23+
gradle wrapper
24+
```
25+
26+
Then, run:
27+
28+
```
29+
./gradlew check assemble
30+
```
31+
32+
This runs all tests and packages the library.
33+
34+
## Features/Implementation Notes
35+
36+
* Supports JSON inputs/outputs, File inputs, and Form inputs.
37+
* Supports collection formats for query parameters: csv, tsv, ssv, pipes.
38+
* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in OpenAPI definitions.
39+
* Implementation of ApiClient is intended to reduce method counts, specifically to benefit Android targets.
40+
41+
<a id="documentation-for-api-endpoints"></a>
42+
## Documentation for API Endpoints
43+
44+
All URIs are relative to *http://petstore.swagger.io/v2*
45+
46+
| Class | Method | HTTP request | Description |
47+
| ------------ | ------------- | ------------- | ------------- |
48+
| *PetApi* | [**addPet**](docs/PetApi.md#addpet) | **POST** /pet | Add a new pet to the store |
49+
| *PetApi* | [**deletePet**](docs/PetApi.md#deletepet) | **DELETE** /pet/{petId} | Deletes a pet |
50+
| *PetApi* | [**findPetsByStatus**](docs/PetApi.md#findpetsbystatus) | **GET** /pet/findByStatus | Finds Pets by status |
51+
| *PetApi* | [**findPetsByTags**](docs/PetApi.md#findpetsbytags) | **GET** /pet/findByTags | Finds Pets by tags |
52+
| *PetApi* | [**getPetById**](docs/PetApi.md#getpetbyid) | **GET** /pet/{petId} | Find pet by ID |
53+
| *PetApi* | [**updatePet**](docs/PetApi.md#updatepet) | **PUT** /pet | Update an existing pet |
54+
| *PetApi* | [**updatePetWithForm**](docs/PetApi.md#updatepetwithform) | **POST** /pet/{petId} | Updates a pet in the store with form data |
55+
| *PetApi* | [**uploadFile**](docs/PetApi.md#uploadfile) | **POST** /pet/{petId}/uploadImage | uploads an image |
56+
| *StoreApi* | [**deleteOrder**](docs/StoreApi.md#deleteorder) | **DELETE** /store/order/{orderId} | Delete purchase order by ID |
57+
| *StoreApi* | [**getInventory**](docs/StoreApi.md#getinventory) | **GET** /store/inventory | Returns pet inventories by status |
58+
| *StoreApi* | [**getOrderById**](docs/StoreApi.md#getorderbyid) | **GET** /store/order/{orderId} | Find purchase order by ID |
59+
| *StoreApi* | [**placeOrder**](docs/StoreApi.md#placeorder) | **POST** /store/order | Place an order for a pet |
60+
| *UserApi* | [**createUser**](docs/UserApi.md#createuser) | **POST** /user | Create user |
61+
| *UserApi* | [**createUsersWithArrayInput**](docs/UserApi.md#createuserswitharrayinput) | **POST** /user/createWithArray | Creates list of users with given input array |
62+
| *UserApi* | [**createUsersWithListInput**](docs/UserApi.md#createuserswithlistinput) | **POST** /user/createWithList | Creates list of users with given input array |
63+
| *UserApi* | [**deleteUser**](docs/UserApi.md#deleteuser) | **DELETE** /user/{username} | Delete user |
64+
| *UserApi* | [**getUserByName**](docs/UserApi.md#getuserbyname) | **GET** /user/{username} | Get user by user name |
65+
| *UserApi* | [**loginUser**](docs/UserApi.md#loginuser) | **GET** /user/login | Logs user into the system |
66+
| *UserApi* | [**logoutUser**](docs/UserApi.md#logoutuser) | **GET** /user/logout | Logs out current logged in user session |
67+
| *UserApi* | [**updateUser**](docs/UserApi.md#updateuser) | **PUT** /user/{username} | Updated user |
68+
69+
70+
<a id="documentation-for-models"></a>
71+
## Documentation for Models
72+
73+
- [org.openapitools.client.models.Category](docs/Category.md)
74+
- [org.openapitools.client.models.ModelApiResponse](docs/ModelApiResponse.md)
75+
- [org.openapitools.client.models.Order](docs/Order.md)
76+
- [org.openapitools.client.models.Pet](docs/Pet.md)
77+
- [org.openapitools.client.models.Tag](docs/Tag.md)
78+
- [org.openapitools.client.models.User](docs/User.md)
79+
80+
81+
<a id="documentation-for-authorization"></a>
82+
## Documentation for Authorization
83+
84+
85+
Authentication schemes defined for the API:
86+
<a id="petstore_auth"></a>
87+
### petstore_auth
88+
89+
- **Type**: OAuth
90+
- **Flow**: implicit
91+
- **Authorization URL**: http://petstore.swagger.io/api/oauth/dialog
92+
- **Scopes**:
93+
- write:pets: modify pets in your account
94+
- read:pets: read your pets
95+
96+
<a id="api_key"></a>
97+
### api_key
98+
99+
- **Type**: API key
100+
- **API key parameter name**: api_key
101+
- **Location**: HTTP header
102+

0 commit comments

Comments
 (0)