-
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Java] Feature/wrapped types and default values #11666
base: master
Are you sure you want to change the base?
Changes from 13 commits
d35c175
c18252c
939e9cb
3493e69
b173d8a
49367c9
cea8c75
17f636a
a574d46
60bf823
fafec77
7e7380f
43c41c8
1a696b9
328a906
1c26ef4
8d96802
dab08d2
d76b733
98b14d1
49d29f2
c23eeb1
9bbc6eb
76392d5
720827f
f2a7ea0
a33cb8e
fbd9726
14038a3
126a825
6c00762
1ab0920
87f6900
166a60c
eee53f5
976dad2
90d8fec
e20c617
7fe6942
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,11 @@ | ||
generatorName: spring | ||
outputDir: samples/openapi3/server/petstore/springboot | ||
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml | ||
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-nullable.yaml | ||
templateDir: modules/openapi-generator/src/main/resources/JavaSpring | ||
additionalProperties: | ||
groupId: org.openapitools.openapi3 | ||
documentationProvider: springdoc | ||
useOptional: true | ||
artifactId: springboot | ||
snapshotVersion: "true" | ||
hideGenerationTimestamp: "true" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,40 +17,72 @@ | |
|
||
package org.openapitools.codegen.languages; | ||
|
||
import static org.openapitools.codegen.utils.StringUtils.camelize; | ||
import static org.openapitools.codegen.utils.StringUtils.escape; | ||
import static org.openapitools.codegen.utils.StringUtils.underscore; | ||
|
||
import com.google.common.base.Strings; | ||
import com.google.common.collect.Sets; | ||
import io.swagger.v3.oas.models.OpenAPI; | ||
import io.swagger.v3.oas.models.Operation; | ||
import io.swagger.v3.oas.models.PathItem; | ||
import io.swagger.v3.oas.models.examples.Example; | ||
import io.swagger.v3.oas.models.media.*; | ||
import io.swagger.v3.oas.models.media.ArraySchema; | ||
import io.swagger.v3.oas.models.media.ComposedSchema; | ||
import io.swagger.v3.oas.models.media.Content; | ||
import io.swagger.v3.oas.models.media.MediaType; | ||
import io.swagger.v3.oas.models.media.Schema; | ||
import io.swagger.v3.oas.models.media.StringSchema; | ||
import io.swagger.v3.oas.models.parameters.Parameter; | ||
import io.swagger.v3.oas.models.parameters.RequestBody; | ||
import io.swagger.v3.oas.models.servers.Server; | ||
import io.swagger.v3.parser.util.SchemaTypeUtil; | ||
import org.apache.commons.io.FilenameUtils; | ||
import org.apache.commons.lang3.BooleanUtils; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.openapitools.codegen.*; | ||
import org.openapitools.codegen.languages.features.DocumentationProviderFeatures; | ||
import org.openapitools.codegen.meta.features.*; | ||
import org.openapitools.codegen.utils.ModelUtils; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.time.LocalDate; | ||
import java.time.ZoneId; | ||
import java.util.*; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.Date; | ||
import java.util.EnumSet; | ||
import java.util.HashMap; | ||
import java.util.Iterator; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.ListIterator; | ||
import java.util.Locale; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
import java.util.concurrent.ConcurrentSkipListSet; | ||
import java.util.regex.Pattern; | ||
import java.util.stream.Stream; | ||
|
||
import static org.openapitools.codegen.utils.StringUtils.*; | ||
import org.apache.commons.io.FilenameUtils; | ||
import org.apache.commons.lang3.BooleanUtils; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.openapitools.codegen.CliOption; | ||
import org.openapitools.codegen.CodegenConfig; | ||
import org.openapitools.codegen.CodegenConstants; | ||
import org.openapitools.codegen.CodegenModel; | ||
import org.openapitools.codegen.CodegenOperation; | ||
import org.openapitools.codegen.CodegenParameter; | ||
import org.openapitools.codegen.CodegenProperty; | ||
import org.openapitools.codegen.DefaultCodegen; | ||
import org.openapitools.codegen.languages.features.DocumentationProviderFeatures; | ||
import org.openapitools.codegen.languages.features.OptionalFeatures; | ||
import org.openapitools.codegen.meta.features.ClientModificationFeature; | ||
import org.openapitools.codegen.meta.features.DocumentationFeature; | ||
import org.openapitools.codegen.meta.features.GlobalFeature; | ||
import org.openapitools.codegen.meta.features.SchemaSupportFeature; | ||
import org.openapitools.codegen.meta.features.SecurityFeature; | ||
import org.openapitools.codegen.meta.features.WireFormatFeature; | ||
import org.openapitools.codegen.utils.ModelUtils; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
public abstract class AbstractJavaCodegen extends DefaultCodegen implements CodegenConfig, | ||
DocumentationProviderFeatures { | ||
DocumentationProviderFeatures, OptionalFeatures { | ||
|
||
private final Logger LOGGER = LoggerFactory.getLogger(AbstractJavaCodegen.class); | ||
private static final String ARTIFACT_VERSION_DEFAULT_VALUE = "1.0.0"; | ||
|
@@ -117,6 +149,7 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code | |
protected String outputTestFolder = ""; | ||
protected DocumentationProvider documentationProvider; | ||
protected AnnotationLibrary annotationLibrary; | ||
protected boolean useOptional = false; | ||
|
||
public AbstractJavaCodegen() { | ||
super(); | ||
|
@@ -585,6 +618,7 @@ public void processOpts() { | |
importMapping.put("IOException", "java.io.IOException"); | ||
importMapping.put("Arrays", "java.util.Arrays"); | ||
importMapping.put("Objects", "java.util.Objects"); | ||
importMapping.put("Optional", "java.util.Optional"); | ||
importMapping.put("StringUtil", invokerPackage + ".StringUtil"); | ||
// import JsonCreator if JsonProperty is imported | ||
// used later in recursive import in postProcessingModels | ||
|
@@ -1287,9 +1321,43 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert | |
|
||
if (openApiNullable) { | ||
if (Boolean.FALSE.equals(property.required) && Boolean.TRUE.equals(property.isNullable)) { | ||
// Only add import when needed | ||
model.imports.add("JsonNullable"); | ||
model.getVendorExtensions().put("x-jackson-optional-nullable-helpers", true); | ||
} | ||
// TODO: adjust other java based mustache templates... | ||
if (SpringCodegen.class.equals(this.getClass()) && Boolean.FALSE.equals(property.required) && Boolean.TRUE.equals(property.isNullable)) { | ||
// Only add import when needed | ||
model.imports.add("JsonIgnore"); | ||
// Wrap dataType and defaults | ||
property.isWrapped = true; | ||
property.wrapperType = "JsonNullable"; | ||
property.wrappedType = "JsonNullable<" + property.datatypeWithEnum + ">"; | ||
property.wrapperFunc = "JsonNullable.of"; | ||
if (property.defaultValue != null) { | ||
property.defaultValue = "JsonNullable.of(" + property.defaultValue + ")"; | ||
} else { | ||
property.defaultValue = "JsonNullable.undefined()"; | ||
} | ||
} | ||
} | ||
if (useOptional) { | ||
if (Boolean.FALSE.equals(property.required) && Boolean.FALSE.equals(property.isNullable) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this misses a case:
I think this would skip There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I think this might still be a problem but I'm happy to iterate on this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You mean it should be: a) Boolean.FALSE.equals(property.required) && Boolean.FALSE.equals(property.isNullable) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes - but happy to follow up with that or merge into my PR dealing with Optionals in POJOs |
||
&& !property.isArray && !property.isMap) { | ||
// Only add import when needed | ||
model.imports.add("JsonIgnore"); | ||
model.imports.add("Optional"); | ||
// Wrap dataType and defaults | ||
property.isWrapped = true; | ||
property.wrapperType = "Optional"; | ||
property.wrappedType = "Optional<" + property.datatypeWithEnum + ">"; | ||
property.wrapperFunc = "Optional.ofNullable"; | ||
if (property.defaultValue != null) { | ||
property.defaultValue = "Optional.of(" + property.defaultValue + ")"; | ||
} else { | ||
property.defaultValue = "Optional.empty()"; | ||
} | ||
} | ||
} | ||
|
||
if (property.isReadOnly) { | ||
|
@@ -1808,6 +1876,11 @@ public void setAnnotationLibrary(AnnotationLibrary annotationLibrary) { | |
this.annotationLibrary = annotationLibrary; | ||
} | ||
|
||
@Override | ||
public void setUseOptional(boolean useOptional) { | ||
this.useOptional = useOptional; | ||
} | ||
|
||
@Override | ||
public String escapeQuotationMark(String input) { | ||
// remove " to avoid code injection | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{{#isWrapped}}{{{wrapperType}}}<{{{datatypeWithEnum}}}>{{/isWrapped}}{{^isWrapped}}{{{datatypeWithEnum}}}{{/isWrapped}} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{{#isWrapped}}{{{wrapperType}}}<{{#useBeanValidation}}{{>beanValidationCore}}{{/useBeanValidation}}{{{datatypeWithEnum}}}>{{/isWrapped}}{{^isWrapped}}{{{datatypeWithEnum}}}{{/isWrapped}} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,39 +49,28 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{^parent}}{{#ha | |
@SerializedName("{{baseName}}") | ||
{{/gson}} | ||
{{#isContainer}} | ||
{{#useBeanValidation}}@Valid{{/useBeanValidation}} | ||
{{#openApiNullable}} | ||
private {{>nullableDataType}} {{name}} = {{#isNullable}}JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#required}}{{{defaultValue}}}{{/required}}{{^required}}null{{/required}}{{/isNullable}}; | ||
{{/openApiNullable}} | ||
{{^openApiNullable}} | ||
private {{>nullableDataType}} {{name}} = {{#required}}{{{defaultValue}}}{{/required}}{{^required}}null{{/required}}; | ||
{{/openApiNullable}} | ||
{{#useBeanValidation}} | ||
@Valid | ||
{{/useBeanValidation}} | ||
{{/isContainer}} | ||
{{^isContainer}} | ||
{{#isDate}} | ||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) | ||
{{/isDate}} | ||
{{#isDateTime}} | ||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) | ||
{{/isDateTime}} | ||
{{#openApiNullable}} | ||
private {{>nullableDataType}} {{name}}{{#isNullable}} = JsonNullable.undefined(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}}; | ||
{{/openApiNullable}} | ||
{{^openApiNullable}} | ||
private {{>nullableDataType}} {{name}}{{#isNullable}} = null{{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}}; | ||
{{/openApiNullable}} | ||
{{/isContainer}} | ||
private {{>dataType}} {{name}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Worth noting: This changes the default way that containers were working before (defaulted to |
||
{{/vars}} | ||
{{#vars}} | ||
|
||
{{! begin feature: fluent setter methods }} | ||
public {{classname}} {{name}}({{{datatypeWithEnum}}} {{name}}) { | ||
{{#openApiNullable}} | ||
this.{{name}} = {{#isNullable}}JsonNullable.of({{name}}){{/isNullable}}{{^isNullable}}{{name}}{{/isNullable}}; | ||
{{/openApiNullable}} | ||
{{^openApiNullable}} | ||
{{#isWrapped}} | ||
this.{{name}} = {{wrapperFunc}}({{name}}); | ||
{{/isWrapped}} | ||
{{^isWrapped}} | ||
this.{{name}} = {{name}}; | ||
{{/openApiNullable}} | ||
{{/isWrapped}} | ||
return this; | ||
} | ||
{{#isArray}} | ||
|
@@ -90,7 +79,7 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{^parent}}{{#ha | |
{{#openApiNullable}} | ||
{{^required}} | ||
if (this.{{name}} == null{{#isNullable}} || !this.{{name}}.isPresent(){{/isNullable}}) { | ||
this.{{name}} = {{#isNullable}}JsonNullable.of({{{defaultValue}}}){{/isNullable}}{{^isNullable}}{{{defaultValue}}}{{/isNullable}}; | ||
this.{{name}} = {{{defaultValue}}}; | ||
} | ||
{{/required}} | ||
this.{{name}}{{#isNullable}}.get(){{/isNullable}}.add({{name}}Item); | ||
|
@@ -137,25 +126,42 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{^parent}}{{#ha | |
{{#vendorExtensions.x-extra-annotation}} | ||
{{{vendorExtensions.x-extra-annotation}}} | ||
{{/vendorExtensions.x-extra-annotation}} | ||
{{^isWrapped}} | ||
{{#useBeanValidation}} | ||
{{>beanValidation}} | ||
{{/useBeanValidation}} | ||
{{/isWrapped}} | ||
{{#swagger2AnnotationLibrary}} | ||
@Schema(name = "{{{baseName}}}", {{#isReadOnly}}accessMode = Schema.AccessMode.READ_ONLY, {{/isReadOnly}}{{#example}}example = "{{{.}}}", {{/example}}{{#description}}description = "{{{.}}}", {{/description}}required = {{{required}}}) | ||
{{/swagger2AnnotationLibrary}} | ||
{{#swagger1AnnotationLibrary}} | ||
@ApiModelProperty({{#example}}example = "{{{.}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}") | ||
{{/swagger1AnnotationLibrary}} | ||
public {{>nullableDataType}} {{getter}}() { | ||
public {{>dataType_beanValidation}} {{getter}}() { | ||
return {{name}}; | ||
} | ||
|
||
{{#vendorExtensions.x-setter-extra-annotation}} | ||
{{{vendorExtensions.x-setter-extra-annotation}}} | ||
{{/vendorExtensions.x-setter-extra-annotation}} | ||
public void {{setter}}({{>nullableDataType}} {{name}}) { | ||
{{^x-expose-wrapper-in-setter}} | ||
{{#isWrapped}} | ||
@JsonIgnore | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be worth documenting why JsonIgnore is needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, what do we need @JsonIgnore for? I added because you have advised to do so. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Jackson will automatically look at all public In order to make sure that this doesn't expect two different fields (one for the |
||
{{/isWrapped}} | ||
public void {{setter}}({{{datatypeWithEnum}}} {{name}}) { | ||
cachescrubber marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{{#isWrapped}} | ||
this.{{name}} = {{wrapperFunc}}({{name}}); | ||
{{/isWrapped}} | ||
{{^isWrapped}} | ||
this.{{name}} = {{name}}; | ||
{{/isWrapped}} | ||
} | ||
{{/x-expose-wrapper-in-setter}} | ||
{{#x-expose-wrapper-in-setter}} | ||
public void {{setter}}({{>dataType}} {{name}}) { | ||
this.{{name}} = {{name}}; | ||
} | ||
{{/x-expose-wrapper-in-setter}} | ||
{{! end feature: getter and setter }} | ||
{{/vars}} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if optionals are enabled, it would be nice for non-nullable non-required maps/arrays to default to empty map/array instead of
null
since an empty list/map has the same meanining as anull
one in that case.