-
Notifications
You must be signed in to change notification settings - Fork 915
Added support for modeling union types. #3124
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
e9d8170
to
3e03931
Compare
|
||
private Type type = Type.UNKNOWN_TO_SDK_VERSION; | ||
|
||
private Set<Type> setTypes = EnumSet.noneOf(Type.class); |
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.
Any reason we need to track multiple types? Spec requirement?
Wondering if we can't just do something like
private Object unionValue;
private Type;
The AllTypesUnionStructure
constructor would need to be changed as well I suppose
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.
To support backwards-compatibility we'd need to support multiple fields being populated, and the SEP specifies that making an existing structure into a union is backwards-compatible.
It's true that things could be simplified if we made switching between union/structures backwards incompatible, because we could guarantee only one field is populated.
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! I thought adding union
to an existing structure would not be backwards compatible so thought this would a safe change.
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.
Digging into the exact wording, I suppose it's less strict than that:
When adding tagged unions to existing SDKs, maintaining backward compatibility comes first. When existing SDKs start to see structure shapes marked with the union trait, they MUST continue to expose the shape as if it is a normal structure; however, if possible, these SDKs SHOULD also expose the shape through a tagged union abstraction. New SDKs or SDKs that are building a new major version do not have such limitations and are not required to expose tagged unions as a kind of subtype of a structure.
|
||
private AllTypesUnionStructure allTypesUnionStructure; | ||
|
||
private Type type = Type.UNKNOWN_TO_SDK_VERSION; |
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 there a use case for distinguishing between "type is unknown" and "no value set"?
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 to my knowledge. WDYT?
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 can't really thing of a good use case for a specific "no value set" Type
value, but I think intuitively, it might more sense to just set this to null
if no members are set?
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.
For responses, UNKNOWN_TO_SDK_VERSION
actually makes sense for zero members because it likely means that the service responded with a type that doesn't exist in the union (so we populated nothing into the structure, because we didn't check that new field). Upgrading the SDK would likely fix the problem, because then the SDK would know about the new type.
For requests, it's a bit more murky, but there's no concept of 'request' and 'response' structures, so we can't meaningfully make a distinction.
More than one member being populated I could see being the thing we might want to differentiate on (return null for the type instead of UNKNOWN_TO_SDK_VERSION), but I didn't want the customer to have to worry about the two different cases for responses, because the "more than one" shouldn't happen.
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.
Ah good point about forwards compatibility on response; though I think null
type in that situation would still suffice no? Since null
would mean none of the members are set.
For clarity I'm advocating for
- null -> no member set
- UNKNOWN_TO_SDK_VERSION -> multiple members set
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.
We use UNKNOWN_TO_SDK_VERSION in enums already to mean "a member we're not aware of, but upgrading your SDK version might help with that", so we'd probably not want to use the same enum name to mean something different ("multiple members").
I like having an enum value to represent "a member we're not aware of", because switching on a null value doesn't work, and we want to ENCOURAGE customers to implement code to handle this case, not the other way around.
If we want to differentiate, we could do
- UNKNOWN_TO_SDK_VERSION -> no member set
- null -> multiple members set
That would preserve the existing meaning of "UNKNOWN_TO_SDK_VERSION" to be "the SDK doesn't know, but upgrading might help", and "null" to mean "the SDK doesn't know, and upgrading wouldn't help".
Again, all of this is about responses. For requests, I agree that these values are not optimal, but customers populated these values so it's less common that they'd be asserting on the type.
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 only other option I can think of would be to let the SDK explicitly tell the union type "the service returned something I don't know" in the unmarshallers to meaningfully set UNKNOWN_TO_SDK_VERSION, and have every other case (0 members on requests, 2+ members on requests, 2+ members on response) have a null type().
The tricky part with that is that we'd then need the unmarshallers to be union-aware (they're currently not) and we'd need some method on the union type to be able to create one that is UNKNOWN_TO_SDK_VERSION. AttributeValue.unknownType()
?
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 like having an enum value to represent "a member we're not aware of", because switching on a null value doesn't work, and we want to ENCOURAGE customers to implement code to handle this case, not the other way around.
Great point! Yes, completely agree with this.
That would preserve the existing meaning of "UNKNOWN_TO_SDK_VERSION" to be "the SDK doesn't know, but upgrading might help", and "null" to mean "the SDK doesn't know, and upgrading wouldn't help".
Nice I like this change
The only other option I can think of would be to let the SDK explicitly tell the union type "the service returned something I don't know" in the unmarshallers to meaningfully set UNKNOWN_TO_SDK_VERSION, and have every other case (0 members on requests, 2+ members on requests, 2+ members on response) have a null type().
The marshaller would need to check for any other members that are not modeled which creates more work done in the marshalling step. I feel like you said, that UNKNOWN_TO_SDK_VERSION
is a much more efficient solution for that.
Are there any union related protocol tests from smithy that we can port to our tests? |
The tests are just around making sure we can marshal and unmarshal unions. Those should be covered by our existing test suite, since unions are just structures with some extra helper methods. Do you think it's worth adding them anyway? |
3e03931
to
32c0365
Compare
Nope in that case I don't think it's necessary. |
Service models marked as a union type have additional helper methods to take advantage of the knowledge that just one member is expected to be set. More information: #3119
…ype() == UNKNOWN_TO_SDK_VERSION.
32c0365
to
4effe63
Compare
Kudos, SonarCloud Quality Gate passed! |
Service models marked as a union type have additional helper methods to take advantage of the knowledge that just one member is expected to be set.
More information: https://github.com/aws/aws-sdk-java-v2/tree/master/docs/design/core/tagged-unions