From d8b4e83279c2670e57790fe3a9438e6bdd47d224 Mon Sep 17 00:00:00 2001 From: asimon Date: Sun, 29 Mar 2026 21:12:07 +0200 Subject: [PATCH 1/3] [spring] Support 'date-time-local' format See https://spec.openapis.org/registry/format/date-time-local.html --- .../languages/AbstractJavaCodegen.java | 3 +- .../java/spring/SpringCodegenTest.java | 29 ++++++++++++++++++ .../resources/3_0/spring/date-time-local.yml | 30 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 modules/openapi-generator/src/test/resources/3_0/spring/date-time-local.yml diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java index 58f7d0e1f226..8519359c3108 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java @@ -662,9 +662,10 @@ public void processOpts() { typeMapping.put("date", "LocalDate"); importMapping.put("LocalDate", "java.time.LocalDate"); importMapping.put("LocalTime", "java.time.LocalTime"); + typeMapping.put("date-time-local", "LocalDateTime"); + importMapping.put("LocalDateTime", "java.time.LocalDateTime"); if ("java8-localdatetime".equals(dateLibrary)) { typeMapping.put("DateTime", "LocalDateTime"); - importMapping.put("LocalDateTime", "java.time.LocalDateTime"); } else { typeMapping.put("DateTime", "OffsetDateTime"); importMapping.put("OffsetDateTime", "java.time.OffsetDateTime"); diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java index 648c8f798c6e..c759e32e1af3 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java @@ -381,6 +381,35 @@ public void generateFormatForDateAndDateTimeQueryParam() throws IOException { .containsWithNameAndAttributes("DateTimeFormat", ImmutableMap.of("iso", "DateTimeFormat.ISO.DATE_TIME")); } + @Test + public void generateLocalDateTimeForLocalDateFormat() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + + OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/3_0/spring/date-time-local.yml", null, new ParseOptions()).getOpenAPI(); + + SpringCodegen codegen = new SpringCodegen(); + codegen.setOutputDir(output.getAbsolutePath()); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false"); + generator.setGenerateMetadata(false); + generator.opts(input).generate(); + + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/model/EventParams.java")) + .hasImports("java.time.LocalDateTime") + .assertProperty("startAt").withType("LocalDateTime"); + } + @Test public void interfaceDefaultImplDisableWithResponseWrapper() { final SpringCodegen codegen = new SpringCodegen(); diff --git a/modules/openapi-generator/src/test/resources/3_0/spring/date-time-local.yml b/modules/openapi-generator/src/test/resources/3_0/spring/date-time-local.yml new file mode 100644 index 000000000000..fd9760c23177 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/spring/date-time-local.yml @@ -0,0 +1,30 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Local Date Format Test +paths: + /events: + get: + operationId: getEvents + parameters: + - in: query + name: eventParams + style: form + explode: true + schema: + $ref: '#/components/schemas/EventParams' + responses: + '200': + description: OK +components: + schemas: + EventParams: + type: object + properties: + startAt: + type: string + format: date-time-local + example: '2024-01-15T10:30:00' + name: + type: string + example: 'My Event' From 662334b26e5579f2c189cb4af68d3beb198c66f2 Mon Sep 17 00:00:00 2001 From: asimon Date: Tue, 31 Mar 2026 21:57:42 +0200 Subject: [PATCH 2/3] feat: simplify test by re-using existing spec --- .../java/spring/SpringCodegenTest.java | 8 ++--- .../resources/3_0/spring/date-time-local.yml | 30 ------------------- .../date-time-parameter-types-for-testing.yml | 4 +++ 3 files changed, 8 insertions(+), 34 deletions(-) delete mode 100644 modules/openapi-generator/src/test/resources/3_0/spring/date-time-local.yml diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java index c759e32e1af3..895ba9820c0a 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java @@ -382,13 +382,13 @@ public void generateFormatForDateAndDateTimeQueryParam() throws IOException { } @Test - public void generateLocalDateTimeForLocalDateFormat() throws IOException { + public void generateLocalDateTimeForDateTimeLocalFormat() throws IOException { File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); output.deleteOnExit(); String outputPath = output.getAbsolutePath().replace('\\', '/'); OpenAPI openAPI = new OpenAPIParser() - .readLocation("src/test/resources/3_0/spring/date-time-local.yml", null, new ParseOptions()).getOpenAPI(); + .readLocation("src/test/resources/3_0/spring/date-time-parameter-types-for-testing.yml", null, new ParseOptions()).getOpenAPI(); SpringCodegen codegen = new SpringCodegen(); codegen.setOutputDir(output.getAbsolutePath()); @@ -405,9 +405,9 @@ public void generateLocalDateTimeForLocalDateFormat() throws IOException { generator.setGenerateMetadata(false); generator.opts(input).generate(); - JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/model/EventParams.java")) + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Pet.java")) .hasImports("java.time.LocalDateTime") - .assertProperty("startAt").withType("LocalDateTime"); + .assertProperty("adoptionDate").withType("LocalDateTime"); } @Test diff --git a/modules/openapi-generator/src/test/resources/3_0/spring/date-time-local.yml b/modules/openapi-generator/src/test/resources/3_0/spring/date-time-local.yml deleted file mode 100644 index fd9760c23177..000000000000 --- a/modules/openapi-generator/src/test/resources/3_0/spring/date-time-local.yml +++ /dev/null @@ -1,30 +0,0 @@ -openapi: 3.0.0 -info: - version: 1.0.0 - title: Local Date Format Test -paths: - /events: - get: - operationId: getEvents - parameters: - - in: query - name: eventParams - style: form - explode: true - schema: - $ref: '#/components/schemas/EventParams' - responses: - '200': - description: OK -components: - schemas: - EventParams: - type: object - properties: - startAt: - type: string - format: date-time-local - example: '2024-01-15T10:30:00' - name: - type: string - example: 'My Event' diff --git a/modules/openapi-generator/src/test/resources/3_0/spring/date-time-parameter-types-for-testing.yml b/modules/openapi-generator/src/test/resources/3_0/spring/date-time-parameter-types-for-testing.yml index 535d152fc96e..ea5873285ec5 100644 --- a/modules/openapi-generator/src/test/resources/3_0/spring/date-time-parameter-types-for-testing.yml +++ b/modules/openapi-generator/src/test/resources/3_0/spring/date-time-parameter-types-for-testing.yml @@ -102,3 +102,7 @@ components: type: string format: date default: '2021-01-01' + adoptionDate: + type: string + format: date-time-local + default: '2007-12-03T10:15:30' \ No newline at end of file From c6859f38db2bf056879f506f80838726f9a1f244 Mon Sep 17 00:00:00 2001 From: asimon Date: Wed, 1 Apr 2026 16:45:10 +0200 Subject: [PATCH 3/3] feat: generate samples --- .../main/java/org/openapitools/model/Pet.java | 30 +++++++++++++- .../main/java/org/openapitools/model/Pet.java | 30 +++++++++++++- .../org/openapitools/server/models/Cat.kt | 13 ++++--- .../org/openapitools/server/models/Dog.kt | 15 ++++--- .../org/openapitools/server/models/Pet.kt | 39 ++++++------------- 5 files changed, 85 insertions(+), 42 deletions(-) diff --git a/samples/client/petstore/spring-cloud-date-time/src/main/java/org/openapitools/model/Pet.java b/samples/client/petstore/spring-cloud-date-time/src/main/java/org/openapitools/model/Pet.java index abb5ef9adbf5..53c655dc292d 100644 --- a/samples/client/petstore/spring-cloud-date-time/src/main/java/org/openapitools/model/Pet.java +++ b/samples/client/petstore/spring-cloud-date-time/src/main/java/org/openapitools/model/Pet.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import java.math.BigDecimal; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.OffsetDateTime; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.lang.Nullable; @@ -40,6 +41,8 @@ public class Pet { @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) private LocalDate dateOfBirth = LocalDate.parse("2021-01-01"); + private LocalDateTime adoptionDate = "2007-12-03T10:15:30"; + public Pet() { super(); } @@ -177,6 +180,27 @@ public void setDateOfBirth(LocalDate dateOfBirth) { this.dateOfBirth = dateOfBirth; } + public Pet adoptionDate(LocalDateTime adoptionDate) { + this.adoptionDate = adoptionDate; + return this; + } + + /** + * Get adoptionDate + * @return adoptionDate + */ + @Valid + @Schema(name = "adoptionDate", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + @JsonProperty("adoptionDate") + public LocalDateTime getAdoptionDate() { + return adoptionDate; + } + + @JsonProperty("adoptionDate") + public void setAdoptionDate(LocalDateTime adoptionDate) { + this.adoptionDate = adoptionDate; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -191,12 +215,13 @@ public boolean equals(Object o) { Objects.equals(this.happy, pet.happy) && Objects.equals(this.price, pet.price) && Objects.equals(this.lastFeed, pet.lastFeed) && - Objects.equals(this.dateOfBirth, pet.dateOfBirth); + Objects.equals(this.dateOfBirth, pet.dateOfBirth) && + Objects.equals(this.adoptionDate, pet.adoptionDate); } @Override public int hashCode() { - return Objects.hash(atType, age, happy, price, lastFeed, dateOfBirth); + return Objects.hash(atType, age, happy, price, lastFeed, dateOfBirth, adoptionDate); } @Override @@ -209,6 +234,7 @@ public String toString() { sb.append(" price: ").append(toIndentedString(price)).append("\n"); sb.append(" lastFeed: ").append(toIndentedString(lastFeed)).append("\n"); sb.append(" dateOfBirth: ").append(toIndentedString(dateOfBirth)).append("\n"); + sb.append(" adoptionDate: ").append(toIndentedString(adoptionDate)).append("\n"); sb.append("}"); return sb.toString(); } diff --git a/samples/openapi3/client/petstore/spring-cloud-date-time/src/main/java/org/openapitools/model/Pet.java b/samples/openapi3/client/petstore/spring-cloud-date-time/src/main/java/org/openapitools/model/Pet.java index abb5ef9adbf5..53c655dc292d 100644 --- a/samples/openapi3/client/petstore/spring-cloud-date-time/src/main/java/org/openapitools/model/Pet.java +++ b/samples/openapi3/client/petstore/spring-cloud-date-time/src/main/java/org/openapitools/model/Pet.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import java.math.BigDecimal; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.OffsetDateTime; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.lang.Nullable; @@ -40,6 +41,8 @@ public class Pet { @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) private LocalDate dateOfBirth = LocalDate.parse("2021-01-01"); + private LocalDateTime adoptionDate = "2007-12-03T10:15:30"; + public Pet() { super(); } @@ -177,6 +180,27 @@ public void setDateOfBirth(LocalDate dateOfBirth) { this.dateOfBirth = dateOfBirth; } + public Pet adoptionDate(LocalDateTime adoptionDate) { + this.adoptionDate = adoptionDate; + return this; + } + + /** + * Get adoptionDate + * @return adoptionDate + */ + @Valid + @Schema(name = "adoptionDate", requiredMode = Schema.RequiredMode.NOT_REQUIRED) + @JsonProperty("adoptionDate") + public LocalDateTime getAdoptionDate() { + return adoptionDate; + } + + @JsonProperty("adoptionDate") + public void setAdoptionDate(LocalDateTime adoptionDate) { + this.adoptionDate = adoptionDate; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -191,12 +215,13 @@ public boolean equals(Object o) { Objects.equals(this.happy, pet.happy) && Objects.equals(this.price, pet.price) && Objects.equals(this.lastFeed, pet.lastFeed) && - Objects.equals(this.dateOfBirth, pet.dateOfBirth); + Objects.equals(this.dateOfBirth, pet.dateOfBirth) && + Objects.equals(this.adoptionDate, pet.adoptionDate); } @Override public int hashCode() { - return Objects.hash(atType, age, happy, price, lastFeed, dateOfBirth); + return Objects.hash(atType, age, happy, price, lastFeed, dateOfBirth, adoptionDate); } @Override @@ -209,6 +234,7 @@ public String toString() { sb.append(" price: ").append(toIndentedString(price)).append("\n"); sb.append(" lastFeed: ").append(toIndentedString(lastFeed)).append("\n"); sb.append(" dateOfBirth: ").append(toIndentedString(dateOfBirth)).append("\n"); + sb.append(" adoptionDate: ").append(toIndentedString(adoptionDate)).append("\n"); sb.append("}"); return sb.toString(); } diff --git a/samples/server/others/kotlin-server/polymorphism/src/main/kotlin/org/openapitools/server/models/Cat.kt b/samples/server/others/kotlin-server/polymorphism/src/main/kotlin/org/openapitools/server/models/Cat.kt index 9daf3b5219e7..e9e835517bae 100644 --- a/samples/server/others/kotlin-server/polymorphism/src/main/kotlin/org/openapitools/server/models/Cat.kt +++ b/samples/server/others/kotlin-server/polymorphism/src/main/kotlin/org/openapitools/server/models/Cat.kt @@ -1,5 +1,5 @@ /** - * Basic polymorphism example without discriminator + * Polymorphism example with allOf and discriminator * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * * The version of the OpenAPI document: 1.0 @@ -11,11 +11,11 @@ */ package org.openapitools.server.models +import org.openapitools.server.models.Pet /** - * A pet cat + * A representation of a cat * @param huntingSkill The measured skill for hunting - * @param petType */ data class Cat( /* The measured skill for hunting */ @@ -23,9 +23,12 @@ data class Cat( @field:com.fasterxml.jackson.annotation.JsonProperty("huntingSkill") val huntingSkill: Cat.HuntingSkill, + @field:com.fasterxml.jackson.annotation.JsonProperty("name") + override val name: kotlin.String, + @field:com.fasterxml.jackson.annotation.JsonProperty("petType") - val petType: kotlin.Any? = null -) + override val petType: kotlin.String +) : Pet(name = name, petType = petType) { /** * The measured skill for hunting diff --git a/samples/server/others/kotlin-server/polymorphism/src/main/kotlin/org/openapitools/server/models/Dog.kt b/samples/server/others/kotlin-server/polymorphism/src/main/kotlin/org/openapitools/server/models/Dog.kt index 803468f25c14..4066cb7a51f8 100644 --- a/samples/server/others/kotlin-server/polymorphism/src/main/kotlin/org/openapitools/server/models/Dog.kt +++ b/samples/server/others/kotlin-server/polymorphism/src/main/kotlin/org/openapitools/server/models/Dog.kt @@ -1,5 +1,5 @@ /** - * Basic polymorphism example without discriminator + * Polymorphism example with allOf and discriminator * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * * The version of the OpenAPI document: 1.0 @@ -11,11 +11,11 @@ */ package org.openapitools.server.models +import org.openapitools.server.models.Pet /** - * A pet dog + * A representation of a dog * @param packSize the size of the pack the dog is from - * @param petType */ data class Dog( /* the size of the pack the dog is from */ @@ -23,7 +23,12 @@ data class Dog( @field:com.fasterxml.jackson.annotation.JsonProperty("packSize") val packSize: kotlin.Int = 0, + @field:com.fasterxml.jackson.annotation.JsonProperty("name") + override val name: kotlin.String, + @field:com.fasterxml.jackson.annotation.JsonProperty("petType") - val petType: kotlin.Any? = null -) + override val petType: kotlin.String +) : Pet(name = name, petType = petType) +{ +} diff --git a/samples/server/others/kotlin-server/polymorphism/src/main/kotlin/org/openapitools/server/models/Pet.kt b/samples/server/others/kotlin-server/polymorphism/src/main/kotlin/org/openapitools/server/models/Pet.kt index 438e3b01ae8d..5812ac1944eb 100644 --- a/samples/server/others/kotlin-server/polymorphism/src/main/kotlin/org/openapitools/server/models/Pet.kt +++ b/samples/server/others/kotlin-server/polymorphism/src/main/kotlin/org/openapitools/server/models/Pet.kt @@ -1,5 +1,5 @@ /** - * Basic polymorphism example without discriminator + * Polymorphism example with allOf and discriminator * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) * * The version of the OpenAPI document: 1.0 @@ -11,42 +11,25 @@ */ package org.openapitools.server.models -import org.openapitools.server.models.Cat -import org.openapitools.server.models.Dog /** * * @param name * @param petType - * @param huntingSkill The measured skill for hunting - * @param packSize the size of the pack the dog is from */ -data class Pet( +@com.fasterxml.jackson.annotation.JsonTypeInfo(use = com.fasterxml.jackson.annotation.JsonTypeInfo.Id.NAME, include = com.fasterxml.jackson.annotation.JsonTypeInfo.As.PROPERTY, property = "petType", visible = true) +@com.fasterxml.jackson.annotation.JsonSubTypes( + com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = Cat::class, name = "Cat"), + com.fasterxml.jackson.annotation.JsonSubTypes.Type(value = Dog::class, name = "Dog") +) +sealed class Pet( @field:com.fasterxml.jackson.annotation.JsonProperty("name") - val name: kotlin.String, + open val name: kotlin.String +, @field:com.fasterxml.jackson.annotation.JsonProperty("petType") - val petType: kotlin.Any?, - /* The measured skill for hunting */ - - @field:com.fasterxml.jackson.annotation.JsonProperty("huntingSkill") - val huntingSkill: Pet.HuntingSkill, - /* the size of the pack the dog is from */ - - @field:com.fasterxml.jackson.annotation.JsonProperty("packSize") - val packSize: kotlin.Int = 0 + open val petType: kotlin.String + ) -{ - /** - * The measured skill for hunting - * Values: clueless,lazy,adventurous,aggressive - */ - enum class HuntingSkill(val value: kotlin.String){ - clueless("clueless"), - lazy("lazy"), - adventurous("adventurous"), - aggressive("aggressive"); - } -}