Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/generators/java-camel.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|
|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|
Expand Down
1 change: 1 addition & 0 deletions docs/generators/spring.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|
|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|
Expand Down
Comment thread
alex-nt marked this conversation as resolved.
Comment thread
alex-nt marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ public class CodegenModel implements IJsonSchemaValidationProperties {
public List<CodegenModel> interfaceModels;
@Getter @Setter
public List<CodegenModel> children;
@Getter @Setter
public List<CodegenModel> directChildren;

// anyOf, oneOf, allOf
public Set<String> anyOf = new TreeSet<>();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 &&
Expand Down Expand Up @@ -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) &&
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -621,16 +621,29 @@ public Map<String, ModelsMap> updateAllModels(Map<String, ModelsMap> objs) {

// Let parent know about all its children
for (Map.Entry<String, CodegenModel> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -113,6 +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 = "useSealed";

@Getter public enum RequestMappingMode {
api_interface("Generate the @RequestMapping annotation on the generated Api Interface."),
Expand Down Expand Up @@ -151,6 +153,7 @@ public class SpringCodegen extends AbstractJavaCodegen
protected boolean performBeanValidation = false;
@Setter protected boolean apiFirst = false;
protected boolean useOptional = false;
@Setter protected boolean useSealed = false;
@Setter protected boolean virtualService = false;
@Setter protected boolean hateoas = false;
@Setter protected boolean returnSuccessCode = false;
Expand Down Expand Up @@ -229,6 +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,
"Whether to generate sealed interfaces and classes"));
Comment thread
alex-nt marked this conversation as resolved.
Outdated
cliOptions.add(CliOption.newBoolean(API_FIRST,
"Generate the API from the OAI spec at server compile time (API first approach)", apiFirst));
cliOptions
Expand Down Expand Up @@ -423,6 +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, this::setUseSealed);
if (getDocumentationProvider().equals(DocumentationProvider.NONE)) {
this.setUseSwaggerUI(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{{>typeInfoAnnotation}}
{{/discriminator}}
{{>generatedAnnotation}}
public interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {
public {{#useSealed}}sealed {{/useSealed}}interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{>sealed}}{
{{#discriminator}}
public {{propertyType}} {{propertyGetter}}();
{{/discriminator}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {{#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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{#useSealed}}{{#oneOf}}{{#-first}}permits {{/-first}}{{.}}{{^-last}}, {{/-last}}{{/oneOf}}{{#directChildren}}{{#-first}}{{#oneOf.isEmpty}}permits {{/oneOf.isEmpty}}{{/-first}}{{classname}}{{^-last}}, {{/-last}}{{/directChildren}} {{/useSealed}}
Original file line number Diff line number Diff line change
Expand Up @@ -1715,6 +1715,50 @@ 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.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<Entity> 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
public void testDiscriminatorWithMappingIssue14731() throws IOException {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
Expand Down