-
-
Notifications
You must be signed in to change notification settings - Fork 7k
Add in Optional
handling for SpringBoot POJOs
#11384
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
Conversation
Bring `welshm` fork up to master
Bring `welshm` fork up to `OpenAPITools` master
Bring `welshm` fork up to latest `OpenAPITools` master
Bring welshm fork up to date with OpenAPI master
Merge lastest to welshm fork for development
- Introduces too much risk for path/header/query params - enums are expected to be primitive types there
@@ -0,0 +1,12 @@ | |||
generatorName: spring | |||
outputDir: samples/openapi3/server/petstore/springboot-nullable-disabled | |||
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore-with-nullable-required.yaml |
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.
Note: This validates against 3_0
@bbdouglas (2017/07) @sreeshas (2017/08) @jfiala (2017/08) @lukoyanov (2017/09) @cbornet (2017/09) @jeff9finger (2018/01) @karismann (2019/03) @Zomzog (2019/04) @lwlee2608 (2019/10) |
Thanks for the PR. Is it correct to say that the uesOptional option will work as expected after this PR gets merged? If other words, this PR aims to fix issues when the useOptional option is set to true, right? |
That is my opinion on how |
- FOO | ||
- BAR | ||
default: BAR | ||
Params: |
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.
@MelleD I tried to cover all relevant cases I could think of here
A quick test shows this to be working with both enabled:
However, I don't see nullability being enforced - if I don't set a required field (such as |
<artifactId>jackson-datatype-jdk8</artifactId> | ||
<version>2.9.6</version> | ||
</dependency> | ||
{{/useOptional}} |
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.
About the maven plug-in test failure, I think you will also need to update modules/openapi-generator-maven-plugin/examples/spring.xml
@welshm You do not need to provide the jackson-datatype-jdk8 dependency. It is provided by spring-boot-starter-json which in turn is a transitive dependency of spring-boot-starter-web . The reason it failed on the maven-plugin test is the outdated version of spring-boot used there (2.2.1.RELEASE). Just update the spring-boot version instead. The Jackson Jdk8Module is configured by default in an recent version of spring-boot. No need to provide it as an @bean. |
public class Category { | ||
|
||
@JsonProperty("id") | ||
private Optional<Long> id; |
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.
You should initialize the member with Optional.empty
A null optional field should never exists.
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.
Good catch - fixed in next patch
private Optional<Long> id; | ||
|
||
@JsonProperty("name") | ||
private Optional<@Pattern(regexp = "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$") String> name; |
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.
Bean validation is duplicated also in the getter. It should add to the getter
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.
Hmm... that becomes tricky because the mustache generates both as {{>optionalDataTypeWithEnum}}
which internally uses {{>optionalDataType}}
which has {{>beanValidationCore}}
as part of the type.
While it is redundant, I am fine with the duplication for how it makes the use of the mustache simpler.
Alernatively, we could have optionalDataTypeWithEnum
and annotatedOptionalDataTypeWithEnum
- but would require making the same distinction for the inner usage of optionalDataType
.
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 see.
So far, optionals have only been used as parameters. I think for member variables we need a new mustache without bean validation. Because the beanvalidation is in <> always makes it awkward.
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.
Created optionalDataTypeWithoutAnnotations
- I did leave it on the setter but I'm not that familiar with how the validation is applied. I can convert it to getter only if that's more correct.
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.
Bean validation for DTOs/Pojos have to be set on getter or fields. Currently the template set it on getter
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.
Will remove from setter then
|
||
@JsonProperty("shipDate") | ||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) | ||
private Optional<OffsetDateTime> shipDate; |
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.
Is this really working with Optional?
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.
It seems to be from my test:
@Test
void jdk8ModuleObjectMapper() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper()
.registerModule(new Jdk8Module().configureAbsentsAsNulls(true))
.registerModule(new JavaTimeModule())
.registerModule(new JsonNullableModule())
;
final Params params = new Params()
.optionalString("OPTIONAL STRING")
.requiredString("REQUIRED STRING")
.requiredNullableString(null)
.requiredEnum(RequiredEnumEnum.ONE)
.optionalDate(LocalDate.of(2020, 10, 2))
.optionalDateTime(OffsetDateTime.of(LocalDateTime.of(2020, 10, 2, 12, 30), ZoneOffset.UTC))
.requiredNumber(123L);
final String jsonResult = objectMapper.writeValueAsString(params);
final Params readBack = objectMapper.readValue(jsonResult, Params.class);
System.console().printf(readBack.toString());
}
I can see the date time and read it back - it matches. JSON output looks fine too.
{{#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}}() { | ||
return {{name}}; | ||
public {{#isNullable}}{{>nullableDataType}}{{/isNullable}}{{^isNullable}}{{#required}}{{>nullableDataType}}{{/required}}{{^required}}{{#isEnum}}{{#useOptional}}Optional<{{/useOptional}}{{{datatypeWithEnum}}}{{#useOptional}}>{{/useOptional}}{{/isEnum}}{{^isEnum}}{{>optionalDataType}}{{/isEnum}}{{/required}}{{/isNullable}} {{getter}}() { |
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 would prefer to use an include here. The code is difficult to read and hard to understand. Isn't it possible to use optionalDataType.mustache?
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.
This comment seems old - but I need to fix the bean annotations anyway so this might change to an include
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.
Old, but still valid IMO
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.
Moved it's own include - I had trouble coming up with a good name so it's dataTypeDeclaration
- which is kind of misleading because these are all data type declarations.
Open to suggestions of better names - and I'm taking a look at your wrapper type change now
@@ -106,7 +106,7 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{^parent}}{{#ha | |||
private {{>nullableDataType}} {{name}}{{#isNullable}} = null{{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}}; | |||
{{/required}} | |||
{{^required}} | |||
private {{>optionalDataTypeWithEnum}} {{name}}{{#isNullable}} = Optional.empty(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = Optional.ofNullable({{{.}}}){{/defaultValue}}{{/isNullable}}; | |||
private {{>optionalDataTypeWithEnum}} {{name}} = {{#defaultValue}}Optional.ofNullable({{{.}}}){{/defaultValue}}{{^defaultValue}}Optional.empty(){{/defaultValue}}; |
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.
Have you considered to override AbstractJavaCodegen#toDefaultValue()
in SpringCodegen
? I would prefer to avoid logic in the templates if possible.
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.
Sure I can look into that. I did it this way because that's how it was already done for JsonNullable but I'll try tk improve it
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.
Not as straight forward as one might think (but doable) I currently doing a proof of concept with JsonNullable.
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.
Might make sense to do it as a separate PR then? Consolidate the defaultValue
decision and generation?
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.
See #11666
@@ -223,15 +223,15 @@ public class {{classname}} {{#parent}}extends {{{.}}}{{/parent}}{{^parent}}{{#ha | |||
public {{>nullableDataType}} {{getter}}() { | |||
{{/isNullable}} | |||
{{^isNullable}} | |||
public {{#isMap}}{{>nullableDataType}}{{/isMap}}{{#isArray}}{{>nullableDataType}}{{/isArray}}{{^isMap}}{{^isArray}}{{#required}}{{>nullableDataType}}{{/required}}{{^required}}{{>optionalDataTypeWithEnum}}{{/required}}{{/isArray}}{{/isMap}} {{getter}}() { | |||
public {{>dataTypeDeclaration}} {{getter}}() { |
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.
Looks good!
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 this can still be improved, but it'll be a lot easier with https://github.com/OpenAPITools/openapi-generator/pull/11666/files so I'll iterate on it after that
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.
Did you want to get #11666 in first? This change is good to go otherwise, but might be easier to deal with merge conflicts and resolve any unit tests changes if I update this after yours is in first.
Closing - since this is way out of date |
Modified
pojo.mustache
to haveOptional
be used on POJOsspring-boot-oas3-nullable.yaml
to generate nullable examples to validate againstspring-boot-oas3-nullable-disabled.yaml
to generate optional behaviour when nullable is disabledNOTE: This is a breaking change for anyone with
useOptional
enabled.PR checklist
This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example
./bin/generate-samples.sh bin/configs/java*
.master
(5.3.0),6.0.x
Spec that shows the use case
Output