From 1b7019fc3ac1762ee5e3ea088b5e34ce6386038f Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Sun, 19 Jan 2025 10:00:30 +0200 Subject: [PATCH 01/14] Generated sealed interfaces for oneOf --- .../codegen/languages/SpringCodegen.java | 5 +++ .../JavaSpring/oneof_interface.mustache | 2 +- .../main/resources/JavaSpring/pojo.mustache | 2 +- .../java/spring/SpringCodegenTest.java | 42 +++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index 9a613c3bb67b..777cf1c896a3 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -113,6 +113,7 @@ public class SpringCodegen extends AbstractJavaCodegen 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"; + public static final String USE_SEALED_INTERFACES = "useSealedInterfaces"; @Getter public enum RequestMappingMode { api_interface("Generate the @RequestMapping annotation on the generated Api Interface."), @@ -151,6 +152,7 @@ public class SpringCodegen extends AbstractJavaCodegen protected boolean performBeanValidation = false; @Setter protected boolean apiFirst = false; protected boolean useOptional = false; + @Setter protected boolean useSealedInterfaces = false; @Setter protected boolean virtualService = false; @Setter protected boolean hateoas = false; @Setter protected boolean returnSuccessCode = false; @@ -229,6 +231,8 @@ public SpringCodegen() { .add(CliOption.newBoolean(USE_BEANVALIDATION, "Use BeanValidation API annotations", useBeanValidation)); cliOptions.add(CliOption.newBoolean(PERFORM_BEANVALIDATION, "Use Bean Validation Impl. to perform BeanValidation", performBeanValidation)); + cliOptions.add(CliOption.newBoolean(USE_SEALED_INTERFACES, + "Whether to generate sealed interfaces for `oneOf` definitions. Will also mark all models as `final`.")); cliOptions.add(CliOption.newBoolean(API_FIRST, "Generate the API from the OAI spec at server compile time (API first approach)", apiFirst)); cliOptions @@ -423,6 +427,7 @@ public void processOpts() { convertPropertyToBooleanAndWriteBack(GENERATE_CONSTRUCTOR_WITH_REQUIRED_ARGS, value -> this.generatedConstructorWithRequiredArgs=value); convertPropertyToBooleanAndWriteBack(RETURN_SUCCESS_CODE, this::setReturnSuccessCode); convertPropertyToBooleanAndWriteBack(USE_SWAGGER_UI, this::setUseSwaggerUI); + convertPropertyToBooleanAndWriteBack(USE_SEALED_INTERFACES, this::setUseSealedInterfaces); if (getDocumentationProvider().equals(DocumentationProvider.NONE)) { this.setUseSwaggerUI(false); } diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache index 679fe3d88315..138559cd38c9 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache @@ -6,7 +6,7 @@ {{>typeInfoAnnotation}} {{/discriminator}} {{>generatedAnnotation}} -public interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { +public interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{#useSealedInterfaces}}permits {{#oneOf}}{{.}}{{^-last}}, {{/-last}}{{/oneOf}} {{/useSealedInterfaces}}{ {{#discriminator}} public {{propertyType}} {{propertyGetter}}(); {{/discriminator}} diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache index a64b34f44a49..73e2572c37d8 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache @@ -31,7 +31,7 @@ {{#vendorExtensions.x-class-extra-annotation}} {{{vendorExtensions.x-class-extra-annotation}}} {{/vendorExtensions.x-class-extra-annotation}} -public class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { +public {{#useSealedInterfaces}}final {{/useSealedInterfaces}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { {{#serializableModel}} private static final long serialVersionUID = 1L; 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 82b92f0082e7..fda9fcb861b3 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 @@ -1715,6 +1715,48 @@ public void testOneOfAndAllOf() throws IOException { assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/PizzaSpeziale.java"), "import java.math.BigDecimal"); } + @Test + public void testOneOfSealed() 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/oneof_polymorphism_and_inheritance.yaml", null, new ParseOptions()).getOpenAPI(); + + SpringCodegen codegen = new SpringCodegen(); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true"); + codegen.setUseOneOfInterfaces(true); + codegen.setUseSealedInterfaces(true); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + codegen.setHateoas(true); + generator.setGenerateMetadata(false); // skip metadata and ↓ only generate models + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "false"); + + codegen.setUseOneOfInterfaces(true); + codegen.setUseSealedInterfaces(true); + codegen.setLegacyDiscriminatorBehavior(false); + + generator.opts(input).generate(); + + assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Foo.java"), "public final class Foo extends Entity implements FooRefOrValue"); + assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRef.java"), "public final class FooRef extends EntityRef implements FooRefOrValue"); + assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRefOrValue.java"), "public interface FooRefOrValue permits Foo, FooRef "); + // previous bugs + JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/model/BarRef.java")) + .fileDoesNotContain("atTypesuper.hashCode", "private String atBaseType"); + // imports for inherited properties + assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/PizzaSpeziale.java"), "import java.math.BigDecimal"); + } + @Test public void testDiscriminatorWithMappingIssue14731() throws IOException { File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); From cf33acd9b45e97c7f53a4fbf5df010c0b50735d4 Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Sun, 19 Jan 2025 10:13:59 +0200 Subject: [PATCH 02/14] Add generated data --- docs/generators/java-camel.md | 1 + docs/generators/spring.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/generators/java-camel.md b/docs/generators/java-camel.md index f6c41195627e..45832b010f70 100644 --- a/docs/generators/java-camel.md +++ b/docs/generators/java-camel.md @@ -103,6 +103,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useOneOfInterfaces|whether to use a java interface to describe a set of oneOf options, where each option is a class that implements the interface| |false| |useOptional|Use Optional container for optional parameters| |false| |useResponseEntity|Use the `ResponseEntity` type to wrap return values of generated API methods. If disabled, method are annotated using a `@ResponseStatus` annotation, which has the status of the first response declared in the Api definition| |true| +|useSealedInterfaces|Whether to generate sealed interfaces for `oneOf` definitions. Will also mark all models as `final`.| |false| |useSpringBoot3|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`.| |false| |useSpringController|Annotate the generated API as a Spring Controller| |false| |useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true| diff --git a/docs/generators/spring.md b/docs/generators/spring.md index ec4a499b39a9..7ce17d553bf1 100644 --- a/docs/generators/spring.md +++ b/docs/generators/spring.md @@ -96,6 +96,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useOneOfInterfaces|whether to use a java interface to describe a set of oneOf options, where each option is a class that implements the interface| |false| |useOptional|Use Optional container for optional parameters| |false| |useResponseEntity|Use the `ResponseEntity` type to wrap return values of generated API methods. If disabled, method are annotated using a `@ResponseStatus` annotation, which has the status of the first response declared in the Api definition| |true| +|useSealedInterfaces|Whether to generate sealed interfaces for `oneOf` definitions. Will also mark all models as `final`.| |false| |useSpringBoot3|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`.| |false| |useSpringController|Annotate the generated API as a Spring Controller| |false| |useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true| From 10fdc9c58138617254648fa6c382e1a7e4d0249f Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Sun, 19 Jan 2025 10:16:45 +0200 Subject: [PATCH 03/14] Add also modifier --- .../src/main/resources/JavaSpring/oneof_interface.mustache | 2 +- .../org/openapitools/codegen/java/spring/SpringCodegenTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache index 138559cd38c9..61b1c2676300 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache @@ -6,7 +6,7 @@ {{>typeInfoAnnotation}} {{/discriminator}} {{>generatedAnnotation}} -public interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{#useSealedInterfaces}}permits {{#oneOf}}{{.}}{{^-last}}, {{/-last}}{{/oneOf}} {{/useSealedInterfaces}}{ +public {{#useSealedInterfaces}}sealed {{/useSealedInterfaces}}interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{#useSealedInterfaces}}permits {{#oneOf}}{{.}}{{^-last}}, {{/-last}}{{/oneOf}} {{/useSealedInterfaces}}{ {{#discriminator}} public {{propertyType}} {{propertyGetter}}(); {{/discriminator}} 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 fda9fcb861b3..5ce5406ac121 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 @@ -1749,7 +1749,7 @@ public void testOneOfSealed() throws IOException { assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Foo.java"), "public final class Foo extends Entity implements FooRefOrValue"); assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRef.java"), "public final class FooRef extends EntityRef implements FooRefOrValue"); - assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRefOrValue.java"), "public interface FooRefOrValue permits Foo, FooRef "); + assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRefOrValue.java"), "public sealed interface FooRefOrValue permits Foo, FooRef "); // previous bugs JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/model/BarRef.java")) .fileDoesNotContain("atTypesuper.hashCode", "private String atBaseType"); From fc96768cc864a777a4368f962a3335bc68a39af5 Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:06:32 +0200 Subject: [PATCH 04/14] Allow sealed for everything --- .../org/openapitools/codegen/CodegenModel.java | 7 +++++++ .../openapitools/codegen/DefaultCodegen.java | 17 +++++++++++++++-- .../codegen/languages/SpringCodegen.java | 1 + .../JavaSpring/oneof_interface.mustache | 2 +- .../src/main/resources/JavaSpring/pojo.mustache | 2 +- .../main/resources/JavaSpring/sealed.mustache | 1 + .../codegen/java/spring/SpringCodegenTest.java | 2 ++ 7 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java index 5303598ed7a6..061be6843855 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java @@ -53,6 +53,8 @@ public class CodegenModel implements IJsonSchemaValidationProperties { public List interfaceModels; @Getter @Setter public List children; + @Getter @Setter + public List directChildren; // anyOf, oneOf, allOf public Set anyOf = new TreeSet<>(); @@ -170,6 +172,7 @@ public class CodegenModel implements IJsonSchemaValidationProperties { public boolean hasOptional; public boolean isArray; public boolean hasChildren; + public boolean hasDirectChildren; public boolean isMap; /** datatype is the generic inner parameter of a std::optional for C++, or Optional (Java) */ public boolean isOptional; @@ -888,6 +891,7 @@ public boolean equals(Object o) { hasOptional == that.hasOptional && isArray == that.isArray && hasChildren == that.hasChildren && + hasDirectChildren == that.hasDirectChildren && isMap == that.isMap && isOptional == that.isOptional && isDeprecated == that.isDeprecated && @@ -922,6 +926,7 @@ public boolean equals(Object o) { Objects.equals(parentModel, that.parentModel) && Objects.equals(interfaceModels, that.interfaceModels) && Objects.equals(children, that.children) && + Objects.equals(directChildren, that.directChildren) && Objects.equals(anyOf, that.anyOf) && Objects.equals(oneOf, that.oneOf) && Objects.equals(allOf, that.allOf) && @@ -1005,6 +1010,7 @@ public String toString() { sb.append(", allParents=").append(allParents); sb.append(", parentModel=").append(parentModel); sb.append(", children=").append(children != null ? children.size() : "[]"); + sb.append(", directChildren=").append(directChildren != null ? directChildren.size() : "[]"); sb.append(", anyOf=").append(anyOf); sb.append(", oneOf=").append(oneOf); sb.append(", allOf=").append(allOf); @@ -1057,6 +1063,7 @@ public String toString() { sb.append(", hasOptional=").append(hasOptional); sb.append(", isArray=").append(isArray); sb.append(", hasChildren=").append(hasChildren); + sb.append(", hasDirectChildren=").append(hasDirectChildren); sb.append(", isMap=").append(isMap); sb.append(", isOptional=").append(isOptional); sb.append(", isDeprecated=").append(isDeprecated); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 434983fb5e06..48ef66c2bcb2 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -621,16 +621,29 @@ public Map updateAllModels(Map objs) { // Let parent know about all its children for (Map.Entry allModelsEntry : allModels.entrySet()) { - String name = allModelsEntry.getKey(); CodegenModel cm = allModelsEntry.getValue(); CodegenModel parent = allModels.get(cm.getParent()); + if (parent != null) { + if (parent.getDirectChildren() == null) { + parent.setDirectChildren(new ArrayList<>()); + } + if (parent.getDirectChildren().stream().map(CodegenModel::getName) + .noneMatch(name -> name.equals(cm.getName()))) { + parent.getDirectChildren().add(cm); + parent.hasDirectChildren = true; + } + } // if a discriminator exists on the parent, don't add this child to the inheritance hierarchy // TODO Determine what to do if the parent discriminator name == the grandparent discriminator name while (parent != null) { if (parent.getChildren() == null) { parent.setChildren(new ArrayList<>()); } - parent.getChildren().add(cm); + if (parent.getChildren().stream().map(CodegenModel::getName) + .noneMatch(name -> name.equals(cm.getName()))) { + parent.getChildren().add(cm); + } + parent.hasChildren = true; Schema parentSchema = this.openAPI.getComponents().getSchemas().get(parent.schemaName); if (parentSchema == null) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index 777cf1c896a3..c5eed672481c 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -52,6 +52,7 @@ import org.openapitools.codegen.CodegenType; import org.openapitools.codegen.SupportingFile; import org.openapitools.codegen.VendorExtension; +import org.openapitools.codegen.config.GlobalSettings; import org.openapitools.codegen.languages.features.BeanValidationFeatures; import org.openapitools.codegen.languages.features.DocumentationProviderFeatures; import org.openapitools.codegen.languages.features.OptionalFeatures; diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache index 61b1c2676300..1f961774af70 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache @@ -6,7 +6,7 @@ {{>typeInfoAnnotation}} {{/discriminator}} {{>generatedAnnotation}} -public {{#useSealedInterfaces}}sealed {{/useSealedInterfaces}}interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{#useSealedInterfaces}}permits {{#oneOf}}{{.}}{{^-last}}, {{/-last}}{{/oneOf}} {{/useSealedInterfaces}}{ +public {{#useSealedInterfaces}}sealed {{/useSealedInterfaces}}interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{>sealed}}{ {{#discriminator}} public {{propertyType}} {{propertyGetter}}(); {{/discriminator}} diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache index 73e2572c37d8..9188a281ce6c 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache @@ -31,7 +31,7 @@ {{#vendorExtensions.x-class-extra-annotation}} {{{vendorExtensions.x-class-extra-annotation}}} {{/vendorExtensions.x-class-extra-annotation}} -public {{#useSealedInterfaces}}final {{/useSealedInterfaces}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} { +public {{#useSealedInterfaces}}{{#hasDirectChildren}}sealed {{/hasDirectChildren}}{{^hasDirectChildren}}final {{/hasDirectChildren}}{{/useSealedInterfaces}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{#useSealedInterfaces}}{{#directChildren}}{{#-first}}permits {{/-first}}{{classname}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealedInterfaces}}{ {{#serializableModel}} private static final long serialVersionUID = 1L; diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache new file mode 100644 index 000000000000..04380bee766b --- /dev/null +++ b/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache @@ -0,0 +1 @@ +{{#useSealedInterfaces}}{{#oneOf}}{{#-first}}permits {{/-first}}{{.}}{{^-last}}, {{/-last}}{{/oneOf}}{{#directChildren}}{{#-first}}{{#oneOf.isEmpty}}permits {{/oneOf.isEmpty}}{{/-first}}{{classname}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealedInterfaces}} \ No newline at end of file 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 5ce5406ac121..9fe162f2fa54 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 @@ -1750,6 +1750,8 @@ public void testOneOfSealed() throws IOException { assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Foo.java"), "public final class Foo extends Entity implements FooRefOrValue"); assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRef.java"), "public final class FooRef extends EntityRef implements FooRefOrValue"); assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRefOrValue.java"), "public sealed interface FooRefOrValue permits Foo, FooRef "); + assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Entity.java"), "public sealed class Entity extends RepresentationModel permits Bar, BarCreate, Foo, Pasta, Pizza"); + // previous bugs JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/model/BarRef.java")) .fileDoesNotContain("atTypesuper.hashCode", "private String atBaseType"); From f555437a52d7d60e8c313096857ddd426456dece Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:16:47 +0200 Subject: [PATCH 05/14] Fully seal model --- docs/generators/java-camel.md | 2 +- docs/generators/spring.md | 2 +- .../openapitools/codegen/languages/SpringCodegen.java | 10 +++++----- .../main/resources/JavaSpring/oneof_interface.mustache | 2 +- .../src/main/resources/JavaSpring/pojo.mustache | 2 +- .../src/main/resources/JavaSpring/sealed.mustache | 2 +- .../codegen/java/spring/SpringCodegenTest.java | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/generators/java-camel.md b/docs/generators/java-camel.md index 45832b010f70..08920318576d 100644 --- a/docs/generators/java-camel.md +++ b/docs/generators/java-camel.md @@ -103,7 +103,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useOneOfInterfaces|whether to use a java interface to describe a set of oneOf options, where each option is a class that implements the interface| |false| |useOptional|Use Optional container for optional parameters| |false| |useResponseEntity|Use the `ResponseEntity` type to wrap return values of generated API methods. If disabled, method are annotated using a `@ResponseStatus` annotation, which has the status of the first response declared in the Api definition| |true| -|useSealedInterfaces|Whether to generate sealed interfaces for `oneOf` definitions. Will also mark all models as `final`.| |false| +|useSealed|Whether to generate sealed interfaces and classes| |false| |useSpringBoot3|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`.| |false| |useSpringController|Annotate the generated API as a Spring Controller| |false| |useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true| diff --git a/docs/generators/spring.md b/docs/generators/spring.md index 7ce17d553bf1..1cab89e36b3d 100644 --- a/docs/generators/spring.md +++ b/docs/generators/spring.md @@ -96,7 +96,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useOneOfInterfaces|whether to use a java interface to describe a set of oneOf options, where each option is a class that implements the interface| |false| |useOptional|Use Optional container for optional parameters| |false| |useResponseEntity|Use the `ResponseEntity` type to wrap return values of generated API methods. If disabled, method are annotated using a `@ResponseStatus` annotation, which has the status of the first response declared in the Api definition| |true| -|useSealedInterfaces|Whether to generate sealed interfaces for `oneOf` definitions. Will also mark all models as `final`.| |false| +|useSealed|Whether to generate sealed interfaces and classes| |false| |useSpringBoot3|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`.| |false| |useSpringController|Annotate the generated API as a Spring Controller| |false| |useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true| diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index c5eed672481c..ea27d6cb3286 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -114,7 +114,7 @@ public class SpringCodegen extends AbstractJavaCodegen 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"; - public static final String USE_SEALED_INTERFACES = "useSealedInterfaces"; + public static final String USE_SEALED = "useSealed"; @Getter public enum RequestMappingMode { api_interface("Generate the @RequestMapping annotation on the generated Api Interface."), @@ -153,7 +153,7 @@ public class SpringCodegen extends AbstractJavaCodegen protected boolean performBeanValidation = false; @Setter protected boolean apiFirst = false; protected boolean useOptional = false; - @Setter protected boolean useSealedInterfaces = false; + @Setter protected boolean useSealed = false; @Setter protected boolean virtualService = false; @Setter protected boolean hateoas = false; @Setter protected boolean returnSuccessCode = false; @@ -232,8 +232,8 @@ public SpringCodegen() { .add(CliOption.newBoolean(USE_BEANVALIDATION, "Use BeanValidation API annotations", useBeanValidation)); cliOptions.add(CliOption.newBoolean(PERFORM_BEANVALIDATION, "Use Bean Validation Impl. to perform BeanValidation", performBeanValidation)); - cliOptions.add(CliOption.newBoolean(USE_SEALED_INTERFACES, - "Whether to generate sealed interfaces for `oneOf` definitions. Will also mark all models as `final`.")); + cliOptions.add(CliOption.newBoolean(USE_SEALED, + "Whether to generate sealed interfaces and classes")); cliOptions.add(CliOption.newBoolean(API_FIRST, "Generate the API from the OAI spec at server compile time (API first approach)", apiFirst)); cliOptions @@ -428,7 +428,7 @@ public void processOpts() { convertPropertyToBooleanAndWriteBack(GENERATE_CONSTRUCTOR_WITH_REQUIRED_ARGS, value -> this.generatedConstructorWithRequiredArgs=value); convertPropertyToBooleanAndWriteBack(RETURN_SUCCESS_CODE, this::setReturnSuccessCode); convertPropertyToBooleanAndWriteBack(USE_SWAGGER_UI, this::setUseSwaggerUI); - convertPropertyToBooleanAndWriteBack(USE_SEALED_INTERFACES, this::setUseSealedInterfaces); + convertPropertyToBooleanAndWriteBack(USE_SEALED, this::setUseSealed); if (getDocumentationProvider().equals(DocumentationProvider.NONE)) { this.setUseSwaggerUI(false); } diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache index 1f961774af70..2739b3103998 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache @@ -6,7 +6,7 @@ {{>typeInfoAnnotation}} {{/discriminator}} {{>generatedAnnotation}} -public {{#useSealedInterfaces}}sealed {{/useSealedInterfaces}}interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{>sealed}}{ +public {{#useSealed}}sealed {{/useSealed}}interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{>sealed}}{ {{#discriminator}} public {{propertyType}} {{propertyGetter}}(); {{/discriminator}} diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache index 9188a281ce6c..47c3143d64a4 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache @@ -31,7 +31,7 @@ {{#vendorExtensions.x-class-extra-annotation}} {{{vendorExtensions.x-class-extra-annotation}}} {{/vendorExtensions.x-class-extra-annotation}} -public {{#useSealedInterfaces}}{{#hasDirectChildren}}sealed {{/hasDirectChildren}}{{^hasDirectChildren}}final {{/hasDirectChildren}}{{/useSealedInterfaces}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{#useSealedInterfaces}}{{#directChildren}}{{#-first}}permits {{/-first}}{{classname}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealedInterfaces}}{ +public {{#useSealed}}{{#hasDirectChildren}}sealed {{/hasDirectChildren}}{{^hasDirectChildren}}final {{/hasDirectChildren}}{{/useSealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{#useSealed}}{{#directChildren}}{{#-first}}permits {{/-first}}{{classname}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealed}}{ {{#serializableModel}} private static final long serialVersionUID = 1L; diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache index 04380bee766b..7a91fdb8f174 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache @@ -1 +1 @@ -{{#useSealedInterfaces}}{{#oneOf}}{{#-first}}permits {{/-first}}{{.}}{{^-last}}, {{/-last}}{{/oneOf}}{{#directChildren}}{{#-first}}{{#oneOf.isEmpty}}permits {{/oneOf.isEmpty}}{{/-first}}{{classname}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealedInterfaces}} \ No newline at end of file +{{#useSealed}}{{#oneOf}}{{#-first}}permits {{/-first}}{{.}}{{^-last}}, {{/-last}}{{/oneOf}}{{#directChildren}}{{#-first}}{{#oneOf.isEmpty}}permits {{/oneOf.isEmpty}}{{/-first}}{{classname}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealed}} \ No newline at end of file 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 9fe162f2fa54..d41f01642acb 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 @@ -1727,7 +1727,7 @@ public void testOneOfSealed() throws IOException { codegen.setOutputDir(output.getAbsolutePath()); codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true"); codegen.setUseOneOfInterfaces(true); - codegen.setUseSealedInterfaces(true); + codegen.setUseSealed(true); ClientOptInput input = new ClientOptInput(); input.openAPI(openAPI); @@ -1742,7 +1742,7 @@ public void testOneOfSealed() throws IOException { generator.setGeneratorPropertyDefault(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "false"); codegen.setUseOneOfInterfaces(true); - codegen.setUseSealedInterfaces(true); + codegen.setUseSealed(true); codegen.setLegacyDiscriminatorBehavior(false); generator.opts(input).generate(); From 5fca635f2ac9ee23eb55fb5fddb9dbf9367bb2f2 Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:03:01 +0200 Subject: [PATCH 06/14] Disable html escaping --- flake.lock | 30 +++++++++++++++---- .../openapitools/codegen/CodegenModel.java | 4 +-- .../main/resources/JavaSpring/pojo.mustache | 2 +- .../main/resources/JavaSpring/sealed.mustache | 2 +- .../java/spring/SpringCodegenTest.java | 8 +---- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/flake.lock b/flake.lock index da1901930c26..f5068e35e84f 100644 --- a/flake.lock +++ b/flake.lock @@ -1,12 +1,15 @@ { "nodes": { "flake-utils": { + "inputs": { + "systems": "systems" + }, "locked": { - "lastModified": 1676283394, - "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -17,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1678083093, - "narHash": "sha256-eTkS9GcdSAYA3cE9zCAAs9wY3+oM2zT45ydIkAcEFFQ=", + "lastModified": 1737989155, + "narHash": "sha256-TFJAGK7tt/jj1v747xNOzopxZ4odBIeDi6EJlYDg/bI=", "owner": "nixos", "repo": "nixpkgs", - "rev": "684306b246d05168e42425a3610df7e2c4d51fcd", + "rev": "5861228f6e9e9dd5d3f8e0a26411f682fdade93a", "type": "github" }, "original": { @@ -35,6 +38,21 @@ "flake-utils": "flake-utils", "nixpkgs": "nixpkgs" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java index 061be6843855..a8dcdd541f9f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java @@ -980,7 +980,7 @@ public boolean equals(Object o) { @Override public int hashCode() { return Objects.hash(getParent(), getParentSchema(), getInterfaces(), getAllParents(), getParentModel(), - getInterfaceModels(), getChildren(), anyOf, oneOf, allOf, getName(), getSchemaName(), getClassname(), getTitle(), + getInterfaceModels(), getChildren(), getDirectChildren(), anyOf, oneOf, allOf, getName(), getSchemaName(), getClassname(), getTitle(), getDescription(), getClassVarName(), getModelJson(), getDataType(), getXmlPrefix(), getXmlNamespace(), getXmlName(), getClassFilename(), getUnescapedDescription(), getDiscriminator(), getDefaultValue(), getArrayModelType(), isAlias, isString, isInteger, isLong, isNumber, isNumeric, isFloat, isDouble, @@ -988,7 +988,7 @@ public int hashCode() { getVars(), getAllVars(), getNonNullableVars(), getRequiredVars(), getOptionalVars(), getReadOnlyVars(), getReadWriteVars(), getParentVars(), getAllowableValues(), getMandatory(), getAllMandatory(), getImports(), hasVars, isEmptyVars(), hasMoreModels, hasEnums, isEnum, isNullable, hasRequired, hasOptional, isArray, - hasChildren, isMap, isOptional, isDeprecated, hasReadOnly, hasOnlyReadOnly, getExternalDocumentation(), getVendorExtensions(), + hasChildren, hasDirectChildren, isMap, isOptional, isDeprecated, hasReadOnly, hasOnlyReadOnly, getExternalDocumentation(), getVendorExtensions(), getAdditionalPropertiesType(), getMaxProperties(), getMinProperties(), getUniqueItems(), getMaxItems(), getMinItems(), getMaxLength(), getMinLength(), getExclusiveMinimum(), getExclusiveMaximum(), getMinimum(), getMaximum(), getPattern(), getMultipleOf(), getItems(), getAdditionalProperties(), getIsModel(), diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache index 47c3143d64a4..d73f8223e3c0 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache @@ -31,7 +31,7 @@ {{#vendorExtensions.x-class-extra-annotation}} {{{vendorExtensions.x-class-extra-annotation}}} {{/vendorExtensions.x-class-extra-annotation}} -public {{#useSealed}}{{#hasDirectChildren}}sealed {{/hasDirectChildren}}{{^hasDirectChildren}}final {{/hasDirectChildren}}{{/useSealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{#useSealed}}{{#directChildren}}{{#-first}}permits {{/-first}}{{classname}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealed}}{ +public {{#useSealed}}{{#hasDirectChildren}}sealed {{/hasDirectChildren}}{{^hasDirectChildren}}final {{/hasDirectChildren}}{{/useSealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{#useSealed}}{{#directChildren}}{{#-first}}permits {{/-first}}{{{classname}}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealed}}{ {{#serializableModel}} private static final long serialVersionUID = 1L; diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache index 7a91fdb8f174..ad6c9fa92aa7 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache @@ -1 +1 @@ -{{#useSealed}}{{#oneOf}}{{#-first}}permits {{/-first}}{{.}}{{^-last}}, {{/-last}}{{/oneOf}}{{#directChildren}}{{#-first}}{{#oneOf.isEmpty}}permits {{/oneOf.isEmpty}}{{/-first}}{{classname}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealed}} \ No newline at end of file +{{#useSealed}}{{#oneOf}}{{#-first}}permits {{/-first}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}{{#directChildren}}{{#-first}}{{#oneOf.isEmpty}}permits {{/oneOf.isEmpty}}{{/-first}}{{{classname}}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealed}} \ No newline at end of file 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 d41f01642acb..7a9cee318175 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 @@ -1716,7 +1716,7 @@ public void testOneOfAndAllOf() throws IOException { } @Test - public void testOneOfSealed() throws IOException { + public void testSealedOneOfAndAllOf() throws IOException { File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); output.deleteOnExit(); String outputPath = output.getAbsolutePath().replace('\\', '/'); @@ -1751,12 +1751,6 @@ public void testOneOfSealed() throws IOException { assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRef.java"), "public final class FooRef extends EntityRef implements FooRefOrValue"); assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRefOrValue.java"), "public sealed interface FooRefOrValue permits Foo, FooRef "); assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Entity.java"), "public sealed class Entity extends RepresentationModel permits Bar, BarCreate, Foo, Pasta, Pizza"); - - // previous bugs - JavaFileAssert.assertThat(Paths.get(outputPath + "/src/main/java/org/openapitools/model/BarRef.java")) - .fileDoesNotContain("atTypesuper.hashCode", "private String atBaseType"); - // imports for inherited properties - assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/PizzaSpeziale.java"), "import java.math.BigDecimal"); } @Test From 31c99b8cce38074da7064713c6bbdafc127ba616 Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:04:13 +0200 Subject: [PATCH 07/14] Update modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java Co-authored-by: martin-mfg <2026226+martin-mfg@users.noreply.github.com> --- .../java/org/openapitools/codegen/languages/SpringCodegen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index ea27d6cb3286..c84af46766ea 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -233,7 +233,7 @@ public SpringCodegen() { cliOptions.add(CliOption.newBoolean(PERFORM_BEANVALIDATION, "Use Bean Validation Impl. to perform BeanValidation", performBeanValidation)); cliOptions.add(CliOption.newBoolean(USE_SEALED, - "Whether to generate sealed interfaces and classes")); + "Whether to generate sealed model interfaces and classes")); cliOptions.add(CliOption.newBoolean(API_FIRST, "Generate the API from the OAI spec at server compile time (API first approach)", apiFirst)); cliOptions From 9ad36c9ac10b2fe66aaa9b10241071ac41cf1cb1 Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:11:40 +0200 Subject: [PATCH 08/14] Update docs --- docs/generators/java-camel.md | 2 +- docs/generators/spring.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/generators/java-camel.md b/docs/generators/java-camel.md index 08920318576d..b0285c683b72 100644 --- a/docs/generators/java-camel.md +++ b/docs/generators/java-camel.md @@ -103,7 +103,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useOneOfInterfaces|whether to use a java interface to describe a set of oneOf options, where each option is a class that implements the interface| |false| |useOptional|Use Optional container for optional parameters| |false| |useResponseEntity|Use the `ResponseEntity` type to wrap return values of generated API methods. If disabled, method are annotated using a `@ResponseStatus` annotation, which has the status of the first response declared in the Api definition| |true| -|useSealed|Whether to generate sealed interfaces and classes| |false| +|useSealed|Whether to generate sealed model interfaces and classes| |false| |useSpringBoot3|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`.| |false| |useSpringController|Annotate the generated API as a Spring Controller| |false| |useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true| diff --git a/docs/generators/spring.md b/docs/generators/spring.md index 1cab89e36b3d..d44b91c69be6 100644 --- a/docs/generators/spring.md +++ b/docs/generators/spring.md @@ -96,7 +96,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |useOneOfInterfaces|whether to use a java interface to describe a set of oneOf options, where each option is a class that implements the interface| |false| |useOptional|Use Optional container for optional parameters| |false| |useResponseEntity|Use the `ResponseEntity` type to wrap return values of generated API methods. If disabled, method are annotated using a `@ResponseStatus` annotation, which has the status of the first response declared in the Api definition| |true| -|useSealed|Whether to generate sealed interfaces and classes| |false| +|useSealed|Whether to generate sealed model interfaces and classes| |false| |useSpringBoot3|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`.| |false| |useSpringController|Annotate the generated API as a Spring Controller| |false| |useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true| From e0826f62f46af4b4c6be163b512ea3f4f9df7ab7 Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Fri, 31 Jan 2025 18:24:05 +0200 Subject: [PATCH 09/14] Check all oneOf scenarios --- .../openapitools/codegen/CodegenModel.java | 15 +-- .../openapitools/codegen/DefaultCodegen.java | 24 ++-- .../JavaSpring/oneof_interface.mustache | 2 +- .../resources/JavaSpring/permits.mustache | 1 + .../main/resources/JavaSpring/pojo.mustache | 2 +- .../main/resources/JavaSpring/sealed.mustache | 2 +- .../java/spring/SpringCodegenTest.java | 112 ++++++++++++------ 7 files changed, 101 insertions(+), 57 deletions(-) create mode 100644 modules/openapi-generator/src/main/resources/JavaSpring/permits.mustache diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java index a8dcdd541f9f..4b2e57aca6aa 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java @@ -53,14 +53,14 @@ public class CodegenModel implements IJsonSchemaValidationProperties { public List interfaceModels; @Getter @Setter public List children; - @Getter @Setter - public List directChildren; // anyOf, oneOf, allOf public Set anyOf = new TreeSet<>(); public Set oneOf = new TreeSet<>(); public Set allOf = new TreeSet<>(); + public List permits = new ArrayList<>(); + // The schema name as written in the OpenAPI document // If it's a reserved word, it will be escaped. @Getter @Setter @@ -172,7 +172,6 @@ public class CodegenModel implements IJsonSchemaValidationProperties { public boolean hasOptional; public boolean isArray; public boolean hasChildren; - public boolean hasDirectChildren; public boolean isMap; /** datatype is the generic inner parameter of a std::optional for C++, or Optional (Java) */ public boolean isOptional; @@ -891,7 +890,6 @@ public boolean equals(Object o) { hasOptional == that.hasOptional && isArray == that.isArray && hasChildren == that.hasChildren && - hasDirectChildren == that.hasDirectChildren && isMap == that.isMap && isOptional == that.isOptional && isDeprecated == that.isDeprecated && @@ -926,7 +924,7 @@ public boolean equals(Object o) { Objects.equals(parentModel, that.parentModel) && Objects.equals(interfaceModels, that.interfaceModels) && Objects.equals(children, that.children) && - Objects.equals(directChildren, that.directChildren) && + Objects.equals(permits, that.permits) && Objects.equals(anyOf, that.anyOf) && Objects.equals(oneOf, that.oneOf) && Objects.equals(allOf, that.allOf) && @@ -980,7 +978,7 @@ public boolean equals(Object o) { @Override public int hashCode() { return Objects.hash(getParent(), getParentSchema(), getInterfaces(), getAllParents(), getParentModel(), - getInterfaceModels(), getChildren(), getDirectChildren(), anyOf, oneOf, allOf, getName(), getSchemaName(), getClassname(), getTitle(), + getInterfaceModels(), getChildren(), anyOf, oneOf, allOf, getName(), getSchemaName(), getClassname(), getTitle(), getDescription(), getClassVarName(), getModelJson(), getDataType(), getXmlPrefix(), getXmlNamespace(), getXmlName(), getClassFilename(), getUnescapedDescription(), getDiscriminator(), getDefaultValue(), getArrayModelType(), isAlias, isString, isInteger, isLong, isNumber, isNumeric, isFloat, isDouble, @@ -988,7 +986,7 @@ public int hashCode() { getVars(), getAllVars(), getNonNullableVars(), getRequiredVars(), getOptionalVars(), getReadOnlyVars(), getReadWriteVars(), getParentVars(), getAllowableValues(), getMandatory(), getAllMandatory(), getImports(), hasVars, isEmptyVars(), hasMoreModels, hasEnums, isEnum, isNullable, hasRequired, hasOptional, isArray, - hasChildren, hasDirectChildren, isMap, isOptional, isDeprecated, hasReadOnly, hasOnlyReadOnly, getExternalDocumentation(), getVendorExtensions(), + hasChildren, isMap, isOptional, isDeprecated, hasReadOnly, hasOnlyReadOnly, getExternalDocumentation(), getVendorExtensions(), getAdditionalPropertiesType(), getMaxProperties(), getMinProperties(), getUniqueItems(), getMaxItems(), getMinItems(), getMaxLength(), getMinLength(), getExclusiveMinimum(), getExclusiveMaximum(), getMinimum(), getMaximum(), getPattern(), getMultipleOf(), getItems(), getAdditionalProperties(), getIsModel(), @@ -1010,7 +1008,7 @@ public String toString() { sb.append(", allParents=").append(allParents); sb.append(", parentModel=").append(parentModel); sb.append(", children=").append(children != null ? children.size() : "[]"); - sb.append(", directChildren=").append(directChildren != null ? directChildren.size() : "[]"); + sb.append(", permits=").append(permits != null ? permits.size() : "[]"); sb.append(", anyOf=").append(anyOf); sb.append(", oneOf=").append(oneOf); sb.append(", allOf=").append(allOf); @@ -1063,7 +1061,6 @@ public String toString() { sb.append(", hasOptional=").append(hasOptional); sb.append(", isArray=").append(isArray); sb.append(", hasChildren=").append(hasChildren); - sb.append(", hasDirectChildren=").append(hasDirectChildren); sb.append(", isMap=").append(isMap); sb.append(", isOptional=").append(isOptional); sb.append(", isDeprecated=").append(isDeprecated); diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 58ee96e07442..afad324e3a12 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -624,13 +624,9 @@ public Map updateAllModels(Map objs) { CodegenModel cm = allModelsEntry.getValue(); CodegenModel parent = allModels.get(cm.getParent()); if (parent != null) { - if (parent.getDirectChildren() == null) { - parent.setDirectChildren(new ArrayList<>()); - } - if (parent.getDirectChildren().stream().map(CodegenModel::getName) + if (parent.permits.stream() .noneMatch(name -> name.equals(cm.getName()))) { - parent.getDirectChildren().add(cm); - parent.hasDirectChildren = true; + parent.permits.add(cm.classname); } } // if a discriminator exists on the parent, don't add this child to the inheritance hierarchy @@ -2499,6 +2495,12 @@ public String getTypeDeclaration(String name) { return name; } + public boolean isCollection(final Schema p) { + Schema schema = unaliasSchema(p); + Schema target = ModelUtils.isGenerateAliasAsModel() ? p : schema; + return ModelUtils.isArraySchema(target) || ModelUtils.isMapSchema(target); + } + /** * Output the language-specific type declaration of the property. * @@ -2717,13 +2719,18 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map languageSpecificPrimitives.contains(dataType) || importMapping.containsKey(dataType)) + .isEmpty()) { + m.permits.add(getTypeDeclaration(interfaceSchema)); + } } } else if (composed.getAllOf() != null) { // no need to add primitive type to allOf, which should comprise of schemas (models) only @@ -2764,6 +2771,9 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, MaptypeInfoAnnotation}} {{/discriminator}} {{>generatedAnnotation}} -public {{#useSealed}}sealed {{/useSealed}}interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{>sealed}}{ +public {{>sealed}}interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{>permits}}{ {{#discriminator}} public {{propertyType}} {{propertyGetter}}(); {{/discriminator}} diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/permits.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/permits.mustache new file mode 100644 index 000000000000..5d92534d6555 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/JavaSpring/permits.mustache @@ -0,0 +1 @@ +{{#useSealed}}{{#permits}}{{#-first}}permits {{/-first}}{{{.}}}{{^-last}}, {{/-last}}{{/permits}} {{/useSealed}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache index d73f8223e3c0..7583f2a1ecdb 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/pojo.mustache @@ -31,7 +31,7 @@ {{#vendorExtensions.x-class-extra-annotation}} {{{vendorExtensions.x-class-extra-annotation}}} {{/vendorExtensions.x-class-extra-annotation}} -public {{#useSealed}}{{#hasDirectChildren}}sealed {{/hasDirectChildren}}{{^hasDirectChildren}}final {{/hasDirectChildren}}{{/useSealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{#useSealed}}{{#directChildren}}{{#-first}}permits {{/-first}}{{{classname}}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealed}}{ +public {{>sealed}}class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}}{{#hateoas}} extends RepresentationModel<{{classname}}> {{/hateoas}}{{/parent}}{{#vendorExtensions.x-implements}}{{#-first}} implements {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{>permits}}{ {{#serializableModel}} private static final long serialVersionUID = 1L; diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache index ad6c9fa92aa7..a5c0af002702 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/sealed.mustache @@ -1 +1 @@ -{{#useSealed}}{{#oneOf}}{{#-first}}permits {{/-first}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}{{#directChildren}}{{#-first}}{{#oneOf.isEmpty}}permits {{/oneOf.isEmpty}}{{/-first}}{{{classname}}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealed}} \ No newline at end of file +{{#useSealed}}{{#permits.0}}sealed {{/permits.0}}{{^permits.0}}{{^vendorExtensions.x-is-one-of-interface}}final {{/vendorExtensions.x-is-one-of-interface}}{{/permits.0}}{{/useSealed}} \ No newline at end of file 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 7a9cee318175..5601fdb8ba31 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 @@ -1715,44 +1715,6 @@ public void testOneOfAndAllOf() throws IOException { assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/PizzaSpeziale.java"), "import java.math.BigDecimal"); } - @Test - public void testSealedOneOfAndAllOf() 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/oneof_polymorphism_and_inheritance.yaml", null, new ParseOptions()).getOpenAPI(); - - SpringCodegen codegen = new SpringCodegen(); - codegen.setOutputDir(output.getAbsolutePath()); - codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true"); - codegen.setUseOneOfInterfaces(true); - codegen.setUseSealed(true); - - ClientOptInput input = new ClientOptInput(); - input.openAPI(openAPI); - input.config(codegen); - - DefaultGenerator generator = new DefaultGenerator(); - codegen.setHateoas(true); - generator.setGenerateMetadata(false); // skip metadata and ↓ only generate models - generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); - generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); - generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); - generator.setGeneratorPropertyDefault(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "false"); - - codegen.setUseOneOfInterfaces(true); - codegen.setUseSealed(true); - codegen.setLegacyDiscriminatorBehavior(false); - - generator.opts(input).generate(); - - assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Foo.java"), "public final class Foo extends Entity implements FooRefOrValue"); - assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRef.java"), "public final class FooRef extends EntityRef implements FooRefOrValue"); - assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/FooRefOrValue.java"), "public sealed interface FooRefOrValue permits Foo, FooRef "); - assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/Entity.java"), "public sealed class Entity extends RepresentationModel permits Bar, BarCreate, Foo, Pasta, Pizza"); - } - @Test public void testDiscriminatorWithMappingIssue14731() throws IOException { File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); @@ -2297,6 +2259,80 @@ public void paramPageableIsNotSpringPaginated_issue13052() throws Exception { .assertParameter("pageable").hasType("Pageable"); } + @DataProvider(name = "sealedScenarios") + public static Object[][] sealedScenarios() { + return new Object[][]{ + {"oneof_polymorphism_and_inheritance.yaml", Map.of( + "Foo.java", "public final class Foo extends Entity implements FooRefOrValue", + "FooRef.java", "public final class FooRef extends EntityRef implements FooRefOrValue", + "FooRefOrValue.java", "public sealed interface FooRefOrValue permits Foo, FooRef ", + "Entity.java", "public sealed class Entity extends RepresentationModel permits Bar, BarCreate, Foo, Pasta, Pizza")}, + {"oneOf_additionalProperties.yaml", Map.of( + "SchemaA.java", "public final class SchemaA extends RepresentationModel implements PostRequest {", + "PostRequest.java", "public sealed interface PostRequest permits SchemaA {")}, + {"oneOf_array.yaml", Map.of( + "MyExampleGet200Response.java", "public interface MyExampleGet200Response")}, + {"oneOf_duplicateArray.yaml", Map.of( + "Example.java", "public interface Example {")}, + {"oneOf_nonPrimitive.yaml", Map.of( + "Example.java", "public interface Example {")}, + {"oneOf_primitive.yaml", Map.of( + "Child.java", "public final class Child extends RepresentationModel implements Example {", + "Example.java", "public sealed interface Example permits Child {")}, + {"oneOf_primitiveAndArray.yaml", Map.of( + "Example.java", "public interface Example {")}, + {"oneOf_reuseRef.yaml", Map.of( + "Fruit.java", "public sealed interface Fruit permits Apple, Banana {", + "Banana.java", "public final class Banana extends RepresentationModel implements Fruit {", + "Apple.java", "public final class Apple extends RepresentationModel implements Fruit {")}, + {"oneOf_twoPrimitives.yaml", Map.of( + "MyExamplePostRequest.java", "public interface MyExamplePostRequest {")}, + {"oneOfArrayMapImport.yaml", Map.of( + "Fruit.java", "public interface Fruit {", + "Grape.java", "public final class Grape extends RepresentationModel {", + "Apple.java", "public final class Apple extends RepresentationModel {")}, + {"oneOfDiscriminator.yaml", Map.of( + "FruitAllOfDisc.java", "public sealed interface FruitAllOfDisc permits AppleAllOfDisc, BananaAllOfDisc {", + "FruitReqDisc.java", "public sealed interface FruitReqDisc permits AppleReqDisc, BananaReqDisc {\n")} + }; + } + + @Test(dataProvider = "sealedScenarios", description = "sealed scenarios") + public void sealedScenarios(String apiFile, Map definitions) 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/" + apiFile, null, new ParseOptions()).getOpenAPI(); + + SpringCodegen codegen = new SpringCodegen(); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true"); + codegen.setUseOneOfInterfaces(true); + codegen.setUseSealed(true); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + codegen.setHateoas(true); + generator.setGenerateMetadata(false); // skip metadata and ↓ only generate models + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "false"); + + codegen.setUseOneOfInterfaces(true); + codegen.setUseSealed(true); + codegen.setLegacyDiscriminatorBehavior(false); + + generator.opts(input).generate(); + + definitions.forEach((file, check) -> + assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/model/" + file), check)); + } + @Test public void shouldSetDefaultValueForMultipleArrayItems() throws IOException { Map additionalProperties = new HashMap<>(); From 02b7b74236649975eede2bb55652aa8a3726e2c5 Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Sat, 1 Feb 2025 09:11:20 +0200 Subject: [PATCH 10/14] Fix failed scenario --- .../src/main/java/org/openapitools/codegen/DefaultCodegen.java | 2 +- .../org/openapitools/codegen/java/spring/SpringCodegenTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index afad324e3a12..752d382a272f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -624,7 +624,7 @@ public Map updateAllModels(Map objs) { CodegenModel cm = allModelsEntry.getValue(); CodegenModel parent = allModels.get(cm.getParent()); if (parent != null) { - if (parent.permits.stream() + if (!parent.permits.contains(cm.classname) && parent.permits.stream() .noneMatch(name -> name.equals(cm.getName()))) { parent.permits.add(cm.classname); } 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 5601fdb8ba31..dc0dec379a33 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 @@ -2266,7 +2266,7 @@ public static Object[][] sealedScenarios() { "Foo.java", "public final class Foo extends Entity implements FooRefOrValue", "FooRef.java", "public final class FooRef extends EntityRef implements FooRefOrValue", "FooRefOrValue.java", "public sealed interface FooRefOrValue permits Foo, FooRef ", - "Entity.java", "public sealed class Entity extends RepresentationModel permits Bar, BarCreate, Foo, Pasta, Pizza")}, + "Entity.java", "public sealed class Entity extends RepresentationModel permits Bar, BarCreate, Foo, Pasta, Pizza {")}, {"oneOf_additionalProperties.yaml", Map.of( "SchemaA.java", "public final class SchemaA extends RepresentationModel implements PostRequest {", "PostRequest.java", "public sealed interface PostRequest permits SchemaA {")}, From 1b49d55d2e4a4d1516804ab6a0d7157cc4a4bbcb Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Sat, 1 Feb 2025 23:11:24 +0200 Subject: [PATCH 11/14] Fix comments --- .../main/java/org/openapitools/codegen/CodegenModel.java | 2 +- .../main/java/org/openapitools/codegen/DefaultCodegen.java | 6 ------ .../openapitools/codegen/java/spring/SpringCodegenTest.java | 2 -- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java index 4b2e57aca6aa..c773300afa1d 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java @@ -978,7 +978,7 @@ public boolean equals(Object o) { @Override public int hashCode() { return Objects.hash(getParent(), getParentSchema(), getInterfaces(), getAllParents(), getParentModel(), - getInterfaceModels(), getChildren(), anyOf, oneOf, allOf, getName(), getSchemaName(), getClassname(), getTitle(), + getInterfaceModels(), getChildren(), permits, anyOf, oneOf, allOf, getName(), getSchemaName(), getClassname(), getTitle(), getDescription(), getClassVarName(), getModelJson(), getDataType(), getXmlPrefix(), getXmlNamespace(), getXmlName(), getClassFilename(), getUnescapedDescription(), getDiscriminator(), getDefaultValue(), getArrayModelType(), isAlias, isString, isInteger, isLong, isNumber, isNumeric, isFloat, isDouble, diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 752d382a272f..04c4527dfe49 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -2725,12 +2725,6 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map languageSpecificPrimitives.contains(dataType) || importMapping.containsKey(dataType)) - .isEmpty()) { - m.permits.add(getTypeDeclaration(interfaceSchema)); - } } } else if (composed.getAllOf() != null) { // no need to add primitive type to allOf, which should comprise of schemas (models) only 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 dc0dec379a33..9e2fed6f44c3 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 @@ -2323,8 +2323,6 @@ public void sealedScenarios(String apiFile, Map definitions) thr generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); generator.setGeneratorPropertyDefault(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, "false"); - codegen.setUseOneOfInterfaces(true); - codegen.setUseSealed(true); codegen.setLegacyDiscriminatorBehavior(false); generator.opts(input).generate(); From 3f407eb1db263644b8a9639126a64ec586b87089 Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Sat, 1 Feb 2025 23:20:50 +0200 Subject: [PATCH 12/14] Remove unused import --- .../java/org/openapitools/codegen/languages/SpringCodegen.java | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java index c84af46766ea..d5466ef16db4 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java @@ -52,7 +52,6 @@ import org.openapitools.codegen.CodegenType; import org.openapitools.codegen.SupportingFile; import org.openapitools.codegen.VendorExtension; -import org.openapitools.codegen.config.GlobalSettings; import org.openapitools.codegen.languages.features.BeanValidationFeatures; import org.openapitools.codegen.languages.features.DocumentationProviderFeatures; import org.openapitools.codegen.languages.features.OptionalFeatures; From 1e0a578adad5cda75bf31014944f2f320ad040c2 Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Sat, 1 Feb 2025 23:43:17 +0200 Subject: [PATCH 13/14] Adapt pom.xml also --- .../resources/JavaSpring/libraries/spring-boot/pom.mustache | 5 +++++ .../JavaSpring/libraries/spring-cloud/pom-sb3.mustache | 5 +++++ .../resources/JavaSpring/libraries/spring-cloud/pom.mustache | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-boot/pom.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-boot/pom.mustache index 26e4aad21fa4..b7f61a2e00aa 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-boot/pom.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-boot/pom.mustache @@ -6,7 +6,12 @@ {{artifactId}} {{artifactVersion}} + {{#useSealed}} + 17 + {{/useSealed}} + {{^useSealed}} 1.8 + {{/useSealed}} ${java.version} ${java.version} UTF-8 diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-cloud/pom-sb3.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-cloud/pom-sb3.mustache index 65bf80bf1c0f..fa503914df40 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-cloud/pom-sb3.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-cloud/pom-sb3.mustache @@ -6,7 +6,12 @@ {{artifactId}} {{artifactVersion}} + {{#useSealed}} + 17 + {{/useSealed}} + {{^useSealed}} 8 + {{/useSealed}} ${java.version} ${java.version} UTF-8 diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-cloud/pom.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-cloud/pom.mustache index 41bbf5ef1b20..909112e8f30e 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-cloud/pom.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-cloud/pom.mustache @@ -6,7 +6,12 @@ {{artifactId}} {{artifactVersion}} + {{#useSealed}} + 17 + {{/useSealed}} + {{^useSealed}} 1.8 + {{/useSealed}} ${java.version} ${java.version} UTF-8 From 0291b11607fae5ee68476d7973429e64fc48c72b Mon Sep 17 00:00:00 2001 From: Alex <12470950+alex-nt@users.noreply.github.com> Date: Sun, 9 Feb 2025 18:59:36 +0200 Subject: [PATCH 14/14] Add comment and remove unused function --- .../main/java/org/openapitools/codegen/CodegenModel.java | 1 + .../main/java/org/openapitools/codegen/DefaultCodegen.java | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java index c773300afa1d..28735ad2df3f 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenModel.java @@ -59,6 +59,7 @@ public class CodegenModel implements IJsonSchemaValidationProperties { public Set oneOf = new TreeSet<>(); public Set allOf = new TreeSet<>(); + // direct descendants that are allowed to extend the current model public List permits = new ArrayList<>(); // The schema name as written in the OpenAPI document diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 04c4527dfe49..2f0b0f948a34 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -2495,12 +2495,6 @@ public String getTypeDeclaration(String name) { return name; } - public boolean isCollection(final Schema p) { - Schema schema = unaliasSchema(p); - Schema target = ModelUtils.isGenerateAliasAsModel() ? p : schema; - return ModelUtils.isArraySchema(target) || ModelUtils.isMapSchema(target); - } - /** * Output the language-specific type declaration of the property. *