-
Notifications
You must be signed in to change notification settings - Fork 45
[Sealed unions] Make toString backward compatible
#2753
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
base: develop
Are you sure you want to change the base?
Conversation
8f69baf to
79f7c1d
Compare
| .addMethod(MethodSpecs.createToString( | ||
| unionClass.simpleName(), | ||
| fields.stream() | ||
| valueFieldList.stream() |
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.
Unneeded traversal.
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 sure I understand this comment, nor why you made the above change, as it doesn't seem like you're using valueField anywhere?
| .addMethod(MethodSpecs.createToString( | ||
| unionClass.simpleName(), | ||
| fields.stream() | ||
| valueFieldList.stream() |
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 sure I understand this comment, nor why you made the above change, as it doesn't seem like you're using valueField anywhere?
| if (sealedUnions) { | ||
| return MethodSpec.methodBuilder("toString") | ||
| .addAnnotation(Override.class) | ||
| .addModifiers(Modifier.PUBLIC) | ||
| .returns(ClassName.get(String.class)) | ||
| .addCode(CodeBlock.builder() | ||
| .add( | ||
| "return $S\n", | ||
| baseClassName.simpleName() + '{' + VALUE_FIELD_NAME + ": " | ||
| + wrapperClassName.simpleName() + '{' + VALUE_FIELD_NAME + ": ") | ||
| .add(" + $N", VALUE_FIELD_NAME) | ||
| .add(" + \"}}\";") | ||
| .build()) | ||
| .addJavadoc( | ||
| "This method is not part of Conjure API. Users should not rely on consistent generation of" | ||
| + " the toString method between versions of Conjure.") | ||
| .build(); | ||
| } else { | ||
| return MethodSpec.methodBuilder("toString") | ||
| .addAnnotation(Override.class) | ||
| .addModifiers(Modifier.PUBLIC) | ||
| .returns(ClassName.get(String.class)) | ||
| .addCode(CodeBlock.builder() | ||
| .add("return $S\n", wrapperClassName.simpleName() + '{' + VALUE_FIELD_NAME + ": ") | ||
| .add(" + $N", VALUE_FIELD_NAME) | ||
| .add(" + '}';") | ||
| .build()) | ||
| .build(); | ||
| } |
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.
Something slightly bugs me in that we are hardcoding the expectation that there is only a single field. I'm not seeing this change in the near-term, but I'm wondering whether we could somehow rely on MethodSpecs.createToString or at least have a similar logic?
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.
A few ideas:
- Have some nested structure passed to
MethodSpecs.createToStringthat can represent arbitrarily nested lists
sealed interface ToStringField permits SimpleField, NestedField {}
record SimpleField(FieldName name) implements ToStringField {}
record NestedField(FieldName name, List<ToStringField> children) implements ToStringField {}I like the design of this, but I didn't follow through with the idea because it made things more complicated for just this one use-case. I think we should explore this if we need to make other nested toString methods.
- Pass in a map that provides a
CodeBlocktoMethodSpecs.createToStringcontaining the representation of the field.
private static CodeBlock toStringConcatenation(
String thisClassName,
List<FieldName> fieldNames,
Map<FieldName, CodeBlock> customFieldBlocks) {...}This feels like a worse version of the idea above: the caller has to copy the logic in MethodSpecs.toStringConcatenation.
Maybe there's another thing you had in mind?
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.
Having special handling for the legacy case seems fine actually. I can't seem to figure out a good way that isn't over-engineered. Relying on the existing method for the normal case seems better though (which I see you've done).
One thing we could add would be a MethodSpecs.createToStringBuilder(CodeBlock) -> MethodSpec.Builder to encode the
MethodSpec.methodBuilder("toString")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(ClassName.get(String.class))part of the logic, but I don't feel strongly about it and it does clutter the code a bit
| .addJavadoc( | ||
| "This method is not part of Conjure API. Users should not rely on consistent generation of" | ||
| + " the toString method between versions of Conjure.") |
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 we just always have this javadoc?
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'm not sure we should include this javadoc in the generation, feels a bit out of place just being on sealed union types. As long as we have a plan to migrate away from the old toString, we should just revert this later once safe.
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 makes it somewhat explicit that people shouldn't be relying on toString's format in general, rather than just because of this specific case
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.
Adding this javadoc to all object toString methods is quite a large diff and somewhat unrelated to this PR. I can open a separate PR adding the javadoc to the generated code in other object types.
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.
As a counter point, there's a lot of things explicitly encoded in our conjure spec that aren't directly included in code/javadocs. Given this is true for all toStrings, it feels better to document this once as opposed to on every conjure object's toString method. I think generally generated code should be just pure output based on the input conjure.
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.
That makes sense. And it's general bad practice to rely on a specific toString format too, so maybe let's not bother
| /** Creates a backward compatible toString method. While in general, Conjure-Java does not provide any guarantees on | ||
| * the toString representation of generated objects, the toString method is currently used when serializing error | ||
| * parameters when JSON is not explicitly used. This is clunky because the sealedUnions implementation does not | ||
| * generate any classes suffixed with `Wrapper`, yet such classes show up in the `toString` implementation. In a | ||
| * future Conjure-Java change, we can update the toString method here to better represent the variant in the | ||
| * sealedUnions implementation. | ||
| */ | ||
| private static MethodSpec createToString( | ||
| boolean sealedUnions, ClassName baseClassName, ClassName wrapperClassName) { |
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 we add some tests to guarantee that this stays true?
| "This method is not part of Conjure API. Users should not rely on consistent generation of" | ||
| + " the toString method between versions of Conjure.") | ||
| .build(); | ||
| } else { |
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.
why would we not continue to use MethodSpecs#createToString for at least the old union types?
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.
Updated. I was trying to create a method that handles only the case where we have value as a field, but it ended up being not as clean as just using the old logic in MethodSpecs
| .addJavadoc( | ||
| "This method is not part of Conjure API. Users should not rely on consistent generation of" | ||
| + " the toString method between versions of Conjure.") |
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'm not sure we should include this javadoc in the generation, feels a bit out of place just being on sealed union types. As long as we have a plan to migrate away from the old toString, we should just revert this later once safe.
dc99860 to
15dc067
Compare
aldexis
left a comment
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.
The current approach seems reasonable to me, provided we do want to do this. If/when we confirm and you remove the do-not-merge, I'm happy to approve
15dc067 to
acb0f8d
Compare
Generate changelog in
|
toString backward compatibletoString backward compatible
✅ Successfully generated changelog entry!Need to regenerate?Simply interact with the changelog bot comment again to regenerate these entries. 📋Changelog Preview💡 Improvements
|
Before this PR
The union objects generated when the
sealedUnionsflag is set createtoStringmethods that are not backward-compatible with objects generated without thesealedUnionsflag. While in generaltoStringmethod implementation is not part of the Conjure API, it's current use in the serialization of error parameters makes rolling out changes totoStringa wire break.After this PR
==COMMIT_MSG==
[Sealed unions] Make
toStringbackward compatible==COMMIT_MSG==
Possible downsides?
Clunky and inaccurate. The
toStringrepresentation of union objects no longer accurately represents the generated object: there will be<Variant>Wrapperstrings in thetoString` representation which do not correspond to any class in the sealed class implementation.