-
-
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
Add Java Optional for POJOs in JavaSpring templates #17202
Conversation
… into add-optional-for-pojos # Conflicts: # modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java
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.
Just some minor comments - I think this is great.
I do think nullable and required can both be true but I don't think it needs to change anything here.
Looking forward to seeing this in a release!
@@ -65,7 +65,7 @@ public class {{classname}}{{#parent}} extends {{{parent}}}{{/parent}}{{^parent}} | |||
{{#isContainer}} | |||
{{#useBeanValidation}}@Valid{{/useBeanValidation}} | |||
{{#openApiNullable}} | |||
private {{>nullableDataType}} {{name}}{{#isNullable}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined(){{/isNullable}}{{^isNullable}}{{#defaultValue}} = {{{.}}}{{/defaultValue}}{{/isNullable}}; | |||
private {{#isNullable}}{{>nullableDataTypeBeanValidation}} {{name}} = JsonNullable.<{{{datatypeWithEnum}}}>undefined();{{/isNullable}}{{^required}}{{^isNullable}}{{>nullableDataTypeBeanValidation}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};{{/isNullable}}{{/required}}{{#required}}{{^isNullable}}{{>nullableDataTypeBeanValidation}} {{name}}{{#defaultValue}} = {{{.}}}{{/defaultValue}};{{/isNullable}}{{/required}} |
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.
Can this be simplified? Looks like it resolves to nullableDataTypeBeanValidation
in several cases.
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 found no other way. Also with the default value etc. different cases
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.
Also don't like that I need now 2 different files with and without bean validation, but this is the issue that Optional need the bean validation between <>
.../openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java
Outdated
Show resolved
Hide resolved
|
||
public Category id(Long id) { | ||
this.id = id; | ||
this.id = Optional.of(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.
Should this be Optional.ofNullable()
? Or is the decision to explicitly disallow setting null
?
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.
Yes that's a good point.
I would say it should not null, because you should not use id(null), because avoiding null values, but maybe a matter of taste.
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.
Can/should there be a clearId()
method then?
Otherwise, the only way to set this to null
or remove the value is to do...
category.setId(Optional.empty())
Maybe we don't need it 🤷 Seems like an edge case anyway
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'll probably ask 4 people and get 5 opinions :D
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 never used a clearXYZ method to handle or clear Optionals, but why not. I would wait for more feedback and then the community maybe can decide which option is better.
Personally i like more set directly Optional.empty()
because then you see you make something special.
But it the same issue with JsonNullable you cannot clear/undefined it with the fluent pattern. You need the setter
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.
Yeah I think setId(Optional.empty()
makes it very clear what's happening - that's a good point.
|
||
public Category id(Long id) { | ||
this.id = id; | ||
this.id = Optional.of(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.
Can/should there be a clearId()
method then?
Otherwise, the only way to set this to null
or remove the value is to do...
category.setId(Optional.empty())
Maybe we don't need it 🤷 Seems like an edge case anyway
@welshm thanks for the quick and fast feedback :). |
@wing328 what do you think here? |
# Conflicts: # modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java
@wing328 new week new try :) |
let me try to review tomorrow or later this week |
thanks for considering backward-compatibility in your PR
it's not a common use case based on my experience and good that you still have it covered in your PR. |
Cool thanks for the feedback |
Hi! Coincidentally, I was trying to figure out how to do this myself in my project and came across this PR. Any idea when a new release with this change will be made generally available? |
If you would try upfront you can copy the three or four files and put in your template dir |
I disagree with this change. First, the code for building a response becomes more verbose. Before: record Entity(String first, String second, String third) {}
public ResponseEntity<Entity> foo(EntityRequest request) {
Entity e = repo.get(request.getId());
return ok(new EntityResponse()
.first(e.first())
.second(e.second())
.third(e.third()));
} After: record Entity(String first, String second, String third) {}
public ResponseEntity<Entity> foo(EntityRequest request) {
Entity e = repo.get(request.getId());
EntityResponse response = new EntityResponse();
// Option 1
if (e.first() != null) {
response.first(e.first());
}
// Option 2
Optional.ofNullable(e.second()).ifPresent(response::second);
// Option 3
response.setThird(Optional.ofNullable(e.third());
return ok(response);
} At the core of this is the fact that the fluent setters do not accept record Entity(Optional<String> first, Optional<String> second) {}
new EntityResponse()
.first(e.first()) // Does not compile
.second(e.second().orElse(null)) // Throws NPE Second, the code in the "Before" snippet still compiles, but has changed to throw an exception. If I did not have test cases that caught this, I might have shipped this upgrade to production and only then discovered these NullPointerExceptions. That this is not listed as a breaking change is very surprising to me. |
I noticed during the update to 7.2.0 that we have a huge number of compilation errors and then came across this PR here. This PR is a massive breaking change It forces the contract provider update his contract accordingly, otherwise you won't be able to update to 7.2.0 (without a massive refactoring) Sadly it does not have its own config toggle for it, but instead simply reuses the existing In the documentation of useOptional the description is as follows: Now there are different variants, none of which is satisfactory:
UPDATE: We just figured out to get the old behaviour back after setting
To anyone who has the same Problem, hope this helps. |
First of all, you should read the description and the ticket in order to understand the change.
Then a few comments on this
That makes absolutely no sense. No contract has been broken by the change. The REST Api + Json structure looks exactly the same. By the way, I hope that you have released your old clients etc., because of course they continue to work because the API is not broken ;)
Really you should read the description first.
I don't know what you're trying to achieve, your code doesn't look like you should use Optionals or JsonNullables. Then just turn it off. |
While it is indeed stated in this PR that this is a breaking change, this PR is not mentioned at all in the release notes. This PR also does not show up if you click the "2 breaking changes (with fallback)" link from the release notes. If this had been in the release notes, I would have found it before starting upgrades.
I'm not advocating for the fluent setter to accept |
Yes, it was probably mislabeled. But after the first generation you probably noticed it straight away.
It is exactly the same as I don't use
I have no idea what you're talking about. I don't see a suggestion for reflective code anywhere. |
If you were only using the regular setters, yes. If you were using the fluent setters: THEN NO. This is a scary change since it will result in runtime exceptions (because of your Thank god we had integration tests for this. Here's a quick example of what's wrong: spec: components:
schemas:
ExampleObj:
type: object
properties:
prop1Required:
type: string
prop2Optional:
type: string
prop3Nullable:
type: string
nullable: true
required:
- prop1Required Generated code om 7.2.0: [...]
private String prop1Required;
private Optional<String> prop2Optional = Optional.empty();
private JsonNullable<String> prop3Nullable = JsonNullable.<String>undefined();
[...getters omitted in this example]
public ExampleObj prop1Required(String prop1Required) {
this.prop1Required = prop1Required;
return this;
}
public void setProp1Required(String prop1Required) {
this.prop1Required = prop1Required;
}
public ExampleObj prop2Optional(String prop2Optional) {
this.prop2Optional = Optional.of(prop2Optional);
return this;
}
public void setProp2Optional(Optional<String> prop2Optional) {
this.prop2Optional = prop2Optional;
}
public ExampleObj prop3Nullable(String prop3Nullable) {
this.prop3Nullable = JsonNullable.of(prop3Nullable);
return this;
}
public void setProp3Nullable(JsonNullable<String> prop3Nullable) {
this.prop3Nullable = prop3Nullable;
} While in 7.1.0 the fluent setter for public ExampleObj prop2Optional(String prop2Optional) {
this.prop2Optional = prop2Optional;
return this;
} The difference is now we can't use our fluent setters for potentially null values! (Since you always assume passing non-null to them for some reason (by using Both versions compile fine, you'll just notice it runtime which is scary. I would at least suggest adding a configOption (similar to the existing |
It depends on your project. In my project all values and parameter are none null. If it is null you have to declare it as Nullable (which is common sense in spring projects also for return values). If not the IDE gives a warning or error and do not compile. So for me it is very scary to use null parameter to set a Optional to empty. I would not do that. Also I see no difference to JsonNullable because you can also not set a JsonNullable to empty with the fluent pattern you have to use the setter. I don‘t see your point why you would like to use Optional when you handling with a lot of null values?
There are different possibilities, but hard to say because your use case is not clear what you try to achieve.
|
Again, we're working with Java, so nullability is a thing. Annotating it as nullable only produces a warning, not a compile-time error.
I can indeed? At least it's not going to throw an exception at me. public ExampleObj prop3Nullable(String prop3Nullable) {
this.prop3Nullable = JsonNullable.of(prop3Nullable);
return this;
} new ExampleObj()
.prop3Nullable(null); Is perfectly legit, since
Especially for deserializing JSON:
respectively.
I don't! That's why I want a config option to turn off the wrapping of non-required values in |
Again no, there are different ways and opinions to handle nulls. One is avoid nulls everywhere and use Optional Just because you don't see the benefit or can't imagine it doesn't mean it's not valid.
It's up to your IDE setting
Again completely different thing and NOT the same thing. JsonNullable tri state: Optional two state:
Then don't use it? https://openapi-generator.tech/docs/generators/spring/
|
I'm not saying it's invalid, I'm just saying that you are opinionating this library and imposing your own preferred code style on this project. I don't care what your opinion (or some random blogger) is regarding this. If Mark Reinhold came and said "we're deprecating null", then it would be a totally different discussion.
Again the world doesn't evolve around you.
Thanks, I will |
You should get more into Java if you call Parlog a random blogger.
No, you're trying to push through your code style and don't understand the point of the PR. I don't have merge rights in this project so the PR is through the normal community process and also a lot of discussions.
Before you start blustering and assuming things about someone, you should probably just read the description. And the option is described directly.
|
This PR has been the worst breaking change in
|
This PR add Optionals for the Spring generator. There are many opinions and discussions on this topic.
See: #14765 (comment)
For this reason, the template was only adapted when using JsonNullable (openApiNullable). The code remains the same for all other templates.
Additionally, useOptional must be used in the code generator.
Then the following will be converted
nullable: true --> JsonNullable<T> foo
required and nullable: false --> <T> foo
none required and nullable: false--> Optional<T> foo
In my view, nullable: true and required make no sense. If you use optionals, this state is not needed. For reasons of consistency, a JsonNullable was used in this case as before.
A JsonNullable is intended for a JSON merge patch (see
https://github.com/OpenAPITools/jackson-databind-nullable). As the name suggests, it is mainly needed for the PATCH operation.
However, the templates support more and the solution was more generic. As before, you can create a JsonNullable for all operations as soon as the attribute nullable: is true.
For fans of Optional everywhere (https://medium.com/97-things/using-optional-nowhere-somewhere-or-everywhere-b1eb5645eab5), there is now finally a way to use Java Optional for POJOs like in the API templates.
This PR is breaking.
PR checklist
Commit all changed files.
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*
.IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
master
@cachescrubber (2022/02) @welshm (2022/02) @MelleD (2022/02) @atextor (2022/02) @manedev79 (2022/02) @javisst (2022/02) @borsch (2022/02) @banlevente (2022/02) @Zomzog (2022/09)
@wing328