Skip to content

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

Merged
merged 3 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/next-release/feature-AWSSDKforJavav2-34a662d.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"category": "AWS SDK for Java v2",
"contributor": "",
"type": "feature",
"description": "Allow services to model structures with mutually exclusive fields (union types). Such structures have additional static constructors and the ability to query for which field is populated. Services which support this feature at launch: accessanalyzer, appconfig, appconfigdata, appmesh, connect, emrcontainers, evidently, grafana, groundstation, healthlake, inspector2, iottwinmaker, migrationhubstrategy, nimble, panorama, proton, rdsdata, redshiftdata, s3control, snowdevicemanagement, ssmincidents, transcribe, wisdom."
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ protected final ShapeModel generateShapeModel(String javaClassName, String shape
shapeModel.withIsEventStream(shape.isEventstream());
shapeModel.withIsEvent(shape.isEvent());
shapeModel.withXmlNamespace(shape.getXmlNamespace());
shapeModel.withIsUnion(shape.isUnion());

boolean hasHeaderMember = false;
boolean hasStatusCodeMember = false;
Expand Down Expand Up @@ -188,6 +189,8 @@ private MemberModel generateMemberModel(String c2jMemberName, Member c2jMemberDe
memberModel.setEventHeader(c2jMemberDefinition.isEventheader());
memberModel.setEndpointDiscoveryId(c2jMemberDefinition.isEndpointdiscoveryid());
memberModel.setXmlAttribute(c2jMemberDefinition.isXmlAttribute());
memberModel.setUnionEnumTypeName(namingStrategy.getUnionEnumTypeName(memberModel));


// Pass the xmlNameSpace from the member reference
if (c2jMemberDefinition.getXmlNamespace() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ public class MemberModel extends DocumentationModel {

private String beanStyleSetterName;

private String unionEnumTypeName;

private boolean isJsonValue;

private String timestampFormat;
Expand Down Expand Up @@ -481,7 +483,7 @@ public String getDeprecatedSetterDocumentation() {
public String getDefaultConsumerFluentSetterDocumentation() {
return (StringUtils.isNotBlank(documentation) ? documentation : defaultSetter().replace("%s", name) + "\n")
+ LF
+ "This is a convenience that creates an instance of the {@link "
+ "This is a convenience method that creates an instance of the {@link "
+ variable.getSimpleType()
+ ".Builder} avoiding the need to create one manually via {@link "
+ variable.getSimpleType()
Expand Down Expand Up @@ -509,6 +511,13 @@ public String getDefaultConsumerFluentSetterDocumentation() {
+ ")";
}

public String getUnionConstructorDocumentation() {
return "Create an instance of this class with {@link #" + this.getFluentGetterMethodName() +
"()} initialized to the given value." +
LF + LF +
getSetterDocumentation();
}

private String getParamDoc() {
return LF
+ "@param "
Expand Down Expand Up @@ -726,4 +735,12 @@ public String getDeprecatedBeanStyleSetterMethodName() {
public void setDeprecatedBeanStyleSetterMethodName(String deprecatedBeanStyleSetterMethodName) {
this.deprecatedBeanStyleSetterMethodName = deprecatedBeanStyleSetterMethodName;
}

public String getUnionEnumTypeName() {
return unionEnumTypeName;
}

public void setUnionEnumTypeName(String unionEnumTypeName) {
this.unionEnumTypeName = unionEnumTypeName;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

package software.amazon.awssdk.codegen.model.intermediate;

import static software.amazon.awssdk.codegen.internal.Constant.LF;
import static software.amazon.awssdk.codegen.internal.Constant.REQUEST_CLASS_SUFFIX;
import static software.amazon.awssdk.codegen.internal.Constant.RESPONSE_CLASS_SUFFIX;
import static software.amazon.awssdk.codegen.internal.DocumentationUtils.removeFromEnd;
Expand Down Expand Up @@ -71,6 +72,8 @@ public class ShapeModel extends DocumentationModel implements HasDeprecation {

private boolean document;

private boolean union;

public ShapeModel() {
}

Expand Down Expand Up @@ -526,6 +529,16 @@ public String getDocumentationShapeName() {
}
}

public String getUnionTypeGetterDocumentation() {
return "Retrieve an enum value representing which member of this object is populated. "
+ LF + LF
+ "When this class is returned in a service response, this will be {@link Type#UNKNOWN_TO_SDK_VERSION} if the "
+ "service returned a member that is only known to a newer SDK version."
+ LF + LF
+ "When this class is created directly in your code, this will be {@link Type#UNKNOWN_TO_SDK_VERSION} if zero "
+ "members are set, and {@code null} if more than one member is set.";
}

@Override
public String toString() {
return shapeName;
Expand Down Expand Up @@ -617,4 +630,12 @@ public ShapeModel withIsDocument(boolean document) {
this.document = document;
return this;
}

public boolean isUnion() {
return union;
}

public void withIsUnion(boolean union) {
this.union = union;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ public class Shape {

private boolean document;

private boolean union;

public boolean isFault() {
return fault;
}
Expand Down Expand Up @@ -318,4 +320,11 @@ public void setDocument(boolean document) {
this.document = document;
}

public boolean isUnion() {
return union;
}

public void setUnion(boolean union) {
this.union = union;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,11 @@ public String getSdkFieldFieldName(MemberModel memberModel) {
return screamCase(memberModel.getName()) + "_FIELD";
}

@Override
public String getUnionEnumTypeName(MemberModel memberModel) {
return screamCase(memberModel.getName());
}

private String rewriteInvalidMemberName(String memberName, Shape parentShape) {
if (isJavaKeyword(memberName) || isDisallowedNameForShape(memberName, parentShape)) {
return Utils.unCapitalize(memberName + CONFLICTING_NAME_SUFFIX);
Expand All @@ -372,6 +377,11 @@ private String rewriteInvalidMemberName(String memberName, Shape parentShape) {
}

private boolean isDisallowedNameForShape(String name, Shape parentShape) {
// Reserve the name "type" for union shapes.
if (parentShape.isUnion() && name.equals("type")) {
return true;
}

if (parentShape.isException()) {
return RESERVED_EXCEPTION_METHOD_NAMES.contains(name);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ public interface NamingStrategy {
*/
String getSdkFieldFieldName(MemberModel memberModel);

/**
* Returns the name of the provided member as if it will be included in an enum (as in, when the parent shape is a union
* and we need to create an enum with each member name in it).
*
* @param memberModel Member to generate the union enum type name for.
*/
String getUnionEnumTypeName(MemberModel memberModel);

/**
* Names a method that would check for existence of the member in the response.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,26 @@ private MethodSpec.Builder setterDeclaration(String methodName, ParameterSpec pa
}

private CodeBlock copySetterBody(String copyAssignment, String regularAssignment, String copyMethodName) {
CodeBlock.Builder body = CodeBlock.builder();

if (shapeModel.isUnion()) {
body.addStatement("Object oldValue = this.$N", fieldName());
}

Optional<ClassName> copierClass = serviceModelCopiers.copierClassFor(memberModel);

return copierClass.map(className -> CodeBlock.builder().addStatement(copyAssignment,
fieldName(),
className,
copyMethodName)
.build())
.orElseGet(() -> CodeBlock.builder().addStatement(regularAssignment, fieldName()).build());
if (copierClass.isPresent()) {
body.addStatement(copyAssignment, fieldName(), copierClass.get(), copyMethodName);
} else {
body.addStatement(regularAssignment, fieldName());
}

if (shapeModel.isUnion()) {
body.addStatement("handleUnionValueChange(Type.$N, oldValue, this.$N)",
memberModel.getUnionEnumTypeName(),
fieldName());
}

return body.build();
}
}
Loading