conjure-java
conjure-java copied to clipboard
Foo.Of method makes all arguments mandatory, including optionals
What happened?
Adding an optional field to an object definition in yml causes the generated object to require the field in its of
method. This technically makes any optional field addition breaking.
e.g. going from
DuplicateRequest:
fields:
parentFolderId: optional<string>
to
DuplicateRequest:
fields:
parentFolderId: optional<string>
foo: optional<string>
generates
@JsonDeserialize(builder = DuplicateRequest.Builder.class)
@Generated("com.palantir.conjure.java.types.BeanGenerator")
public final class DuplicateRequest {
...
public static DuplicateRequest of(String parentFolderId, String foo) {
return builder().parentFolderId(Optional.of(parentFolderId)).foo(Optional.of(foo)).build();
}
}
A few things seem off wrong here:
-
of
shouldn't require optional fields - they should be optional or hidden via overloading - adding an optional field should preserve the previous
of
methods in case consumers were already using them - removing them
What did you want to happen?
Some combination of:
- The
of
method should require optionals for optional fields. - The
of
method should be overloaded for all possible argument combinations given optional arguments - The
of
method only takes required fields, ignoring optional ones so there's no chance of breaks - No
of
method in the first place
I'm making the assumption that the method signature change here is a (small) break.
We've hotly debated this a couple of times, I think what I'd propose is:
- we eliminate
of
method generation from the-api
projects - (low confidence) we consider offering a
-utils
project that includes of methods and other convenience classes
To be clear, adding an optional field is not a wire format break. Your issue is describing a compilation break that users of the .of
fields might encounter.
I like the sound of making the .of
factory take only required fields though, because if you're willing to add a new required field (i.e. impose a wire-break) then it's fine to impose a compilation break?
Note that this still doesn't give us 100% wire format compatibility though, as users are free to rename conjure types (e.g. DuplicateRequest
-> DuplicateItemRequest
) which is wire-format compatible but would obviously result in a compilation break.
Another point in favor of removing the of
method is that it breaks backward compatibility of the produced jars, since the signature of the of
method can change due to a backward compatible change in a object definition. This can result in MethodNotFound
errors at runtime (even when using things like gradle-consistent-versions) because dependencies may have been compiled against an older version of an API whose of
method has a different signature.
This has just bitten us again on an internal project (PDS-399497). Is a potential path forward here with making a change to of
methods behind a config flag?
I'd propose something like this option:
conjure {
java {
omitOptionalFieldsFromOfMethods = true
}
}
Where now an object like this:
MyObject:
fields:
myRequiredField: string
myOptionalField: optional<string>
would have its generated of
method have a parameter for myRequiredField
but not for myOptionalField
.
After speaking with @carterkozak, we'd prefer omitGeneratedOfMethods = true
to remove the of
factory methods entirely, instead of a half-measure that changed the method structure.
Code change is potentially around here: https://github.com/palantir/conjure-java/blob/771a824da8320881509d80da929ba87d37bf3d15/conjure-java-core/src/main/java/com/palantir/conjure/java/types/BeanGenerator.java#L129