Skip to content

Commit cd7cdb1

Browse files
alex-ntmartin-mfg
andauthored
Add option to generate a fully sealed model in the JavaSpring generator (#20503)
* Generated sealed interfaces for oneOf * Add generated data * Add also modifier * Allow sealed for everything * Fully seal model * Disable html escaping * 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> * Update docs * Check all oneOf scenarios * Fix failed scenario * Fix comments * Remove unused import * Adapt pom.xml also * Add comment and remove unused function --------- Co-authored-by: martin-mfg <2026226+martin-mfg@users.noreply.github.com>
1 parent d5866fe commit cd7cdb1

14 files changed

Lines changed: 142 additions & 12 deletions

File tree

docs/generators/java-camel.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
103103
|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|
104104
|useOptional|Use Optional container for optional parameters| |false|
105105
|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|
106+
|useSealed|Whether to generate sealed model interfaces and classes| |false|
106107
|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|
107108
|useSpringController|Annotate the generated API as a Spring Controller| |false|
108109
|useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true|

docs/generators/spring.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
9696
|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|
9797
|useOptional|Use Optional container for optional parameters| |false|
9898
|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|
99+
|useSealed|Whether to generate sealed model interfaces and classes| |false|
99100
|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|
100101
|useSpringController|Annotate the generated API as a Spring Controller| |false|
101102
|useSwaggerUI|Open the OpenApi specification in swagger-ui. Will also import and configure needed dependencies| |true|

flake.lock

Lines changed: 24 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ public class CodegenModel implements IJsonSchemaValidationProperties {
5959
public Set<String> oneOf = new TreeSet<>();
6060
public Set<String> allOf = new TreeSet<>();
6161

62+
// direct descendants that are allowed to extend the current model
63+
public List<String> permits = new ArrayList<>();
64+
6265
// The schema name as written in the OpenAPI document
6366
// If it's a reserved word, it will be escaped.
6467
@Getter @Setter
@@ -922,6 +925,7 @@ public boolean equals(Object o) {
922925
Objects.equals(parentModel, that.parentModel) &&
923926
Objects.equals(interfaceModels, that.interfaceModels) &&
924927
Objects.equals(children, that.children) &&
928+
Objects.equals(permits, that.permits) &&
925929
Objects.equals(anyOf, that.anyOf) &&
926930
Objects.equals(oneOf, that.oneOf) &&
927931
Objects.equals(allOf, that.allOf) &&
@@ -975,7 +979,7 @@ public boolean equals(Object o) {
975979
@Override
976980
public int hashCode() {
977981
return Objects.hash(getParent(), getParentSchema(), getInterfaces(), getAllParents(), getParentModel(),
978-
getInterfaceModels(), getChildren(), anyOf, oneOf, allOf, getName(), getSchemaName(), getClassname(), getTitle(),
982+
getInterfaceModels(), getChildren(), permits, anyOf, oneOf, allOf, getName(), getSchemaName(), getClassname(), getTitle(),
979983
getDescription(), getClassVarName(), getModelJson(), getDataType(), getXmlPrefix(), getXmlNamespace(),
980984
getXmlName(), getClassFilename(), getUnescapedDescription(), getDiscriminator(), getDefaultValue(),
981985
getArrayModelType(), isAlias, isString, isInteger, isLong, isNumber, isNumeric, isFloat, isDouble,
@@ -1005,6 +1009,7 @@ public String toString() {
10051009
sb.append(", allParents=").append(allParents);
10061010
sb.append(", parentModel=").append(parentModel);
10071011
sb.append(", children=").append(children != null ? children.size() : "[]");
1012+
sb.append(", permits=").append(permits != null ? permits.size() : "[]");
10081013
sb.append(", anyOf=").append(anyOf);
10091014
sb.append(", oneOf=").append(oneOf);
10101015
sb.append(", allOf=").append(allOf);

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -621,16 +621,25 @@ public Map<String, ModelsMap> updateAllModels(Map<String, ModelsMap> objs) {
621621

622622
// Let parent know about all its children
623623
for (Map.Entry<String, CodegenModel> allModelsEntry : allModels.entrySet()) {
624-
String name = allModelsEntry.getKey();
625624
CodegenModel cm = allModelsEntry.getValue();
626625
CodegenModel parent = allModels.get(cm.getParent());
626+
if (parent != null) {
627+
if (!parent.permits.contains(cm.classname) && parent.permits.stream()
628+
.noneMatch(name -> name.equals(cm.getName()))) {
629+
parent.permits.add(cm.classname);
630+
}
631+
}
627632
// if a discriminator exists on the parent, don't add this child to the inheritance hierarchy
628633
// TODO Determine what to do if the parent discriminator name == the grandparent discriminator name
629634
while (parent != null) {
630635
if (parent.getChildren() == null) {
631636
parent.setChildren(new ArrayList<>());
632637
}
633-
parent.getChildren().add(cm);
638+
if (parent.getChildren().stream().map(CodegenModel::getName)
639+
.noneMatch(name -> name.equals(cm.getName()))) {
640+
parent.getChildren().add(cm);
641+
}
642+
634643
parent.hasChildren = true;
635644
Schema parentSchema = this.openAPI.getComponents().getSchemas().get(parent.schemaName);
636645
if (parentSchema == null) {
@@ -2704,7 +2713,6 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map<S
27042713
LOGGER.debug("{} (anyOf schema) already has `{}` defined and therefore it's skipped.", m.name, languageType);
27052714
} else {
27062715
m.anyOf.add(languageType);
2707-
27082716
}
27092717
} else if (composed.getOneOf() != null) {
27102718
if (m.oneOf.contains(languageType)) {
@@ -2751,6 +2759,9 @@ protected void updateModelForComposedSchema(CodegenModel m, Schema schema, Map<S
27512759
m.anyOf.add(modelName);
27522760
} else if (composed.getOneOf() != null) {
27532761
m.oneOf.add(modelName);
2762+
if (!m.permits.contains(modelName)) {
2763+
m.permits.add(modelName);
2764+
}
27542765
} else if (composed.getAllOf() != null) {
27552766
m.allOf.add(modelName);
27562767
} else {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public class SpringCodegen extends AbstractJavaCodegen
113113
public static final String REQUEST_MAPPING_OPTION = "requestMappingMode";
114114
public static final String USE_REQUEST_MAPPING_ON_CONTROLLER = "useRequestMappingOnController";
115115
public static final String USE_REQUEST_MAPPING_ON_INTERFACE = "useRequestMappingOnInterface";
116+
public static final String USE_SEALED = "useSealed";
116117

117118
@Getter public enum RequestMappingMode {
118119
api_interface("Generate the @RequestMapping annotation on the generated Api Interface."),
@@ -151,6 +152,7 @@ public class SpringCodegen extends AbstractJavaCodegen
151152
protected boolean performBeanValidation = false;
152153
@Setter protected boolean apiFirst = false;
153154
protected boolean useOptional = false;
155+
@Setter protected boolean useSealed = false;
154156
@Setter protected boolean virtualService = false;
155157
@Setter protected boolean hateoas = false;
156158
@Setter protected boolean returnSuccessCode = false;
@@ -229,6 +231,8 @@ public SpringCodegen() {
229231
.add(CliOption.newBoolean(USE_BEANVALIDATION, "Use BeanValidation API annotations", useBeanValidation));
230232
cliOptions.add(CliOption.newBoolean(PERFORM_BEANVALIDATION,
231233
"Use Bean Validation Impl. to perform BeanValidation", performBeanValidation));
234+
cliOptions.add(CliOption.newBoolean(USE_SEALED,
235+
"Whether to generate sealed model interfaces and classes"));
232236
cliOptions.add(CliOption.newBoolean(API_FIRST,
233237
"Generate the API from the OAI spec at server compile time (API first approach)", apiFirst));
234238
cliOptions
@@ -423,6 +427,7 @@ public void processOpts() {
423427
convertPropertyToBooleanAndWriteBack(GENERATE_CONSTRUCTOR_WITH_REQUIRED_ARGS, value -> this.generatedConstructorWithRequiredArgs=value);
424428
convertPropertyToBooleanAndWriteBack(RETURN_SUCCESS_CODE, this::setReturnSuccessCode);
425429
convertPropertyToBooleanAndWriteBack(USE_SWAGGER_UI, this::setUseSwaggerUI);
430+
convertPropertyToBooleanAndWriteBack(USE_SEALED, this::setUseSealed);
426431
if (getDocumentationProvider().equals(DocumentationProvider.NONE)) {
427432
this.setUseSwaggerUI(false);
428433
}

modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-boot/pom.mustache

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
<name>{{artifactId}}</name>
77
<version>{{artifactVersion}}</version>
88
<properties>
9+
{{#useSealed}}
10+
<java.version>17</java.version>
11+
{{/useSealed}}
12+
{{^useSealed}}
913
<java.version>1.8</java.version>
14+
{{/useSealed}}
1015
<maven.compiler.source>${java.version}</maven.compiler.source>
1116
<maven.compiler.target>${java.version}</maven.compiler.target>
1217
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-cloud/pom-sb3.mustache

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
<name>{{artifactId}}</name>
77
<version>{{artifactVersion}}</version>
88
<properties>
9+
{{#useSealed}}
10+
<java.version>17</java.version>
11+
{{/useSealed}}
12+
{{^useSealed}}
913
<java.version>8</java.version>
14+
{{/useSealed}}
1015
<maven.compiler.source>${java.version}</maven.compiler.source>
1116
<maven.compiler.target>${java.version}</maven.compiler.target>
1217
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

modules/openapi-generator/src/main/resources/JavaSpring/libraries/spring-cloud/pom.mustache

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
<name>{{artifactId}}</name>
77
<version>{{artifactVersion}}</version>
88
<properties>
9+
{{#useSealed}}
10+
<java.version>17</java.version>
11+
{{/useSealed}}
12+
{{^useSealed}}
913
<java.version>1.8</java.version>
14+
{{/useSealed}}
1015
<maven.compiler.source>${java.version}</maven.compiler.source>
1116
<maven.compiler.target>${java.version}</maven.compiler.target>
1217
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

modules/openapi-generator/src/main/resources/JavaSpring/oneof_interface.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{{>typeInfoAnnotation}}
77
{{/discriminator}}
88
{{>generatedAnnotation}}
9-
public interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {
9+
public {{>sealed}}interface {{classname}}{{#vendorExtensions.x-implements}}{{#-first}} extends {{{.}}}{{/-first}}{{^-first}}, {{{.}}}{{/-first}}{{/vendorExtensions.x-implements}} {{>permits}}{
1010
{{#discriminator}}
1111
public {{propertyType}} {{propertyGetter}}();
1212
{{/discriminator}}

0 commit comments

Comments
 (0)