Skip to content

Commit 77386f6

Browse files
authored
fix: XML Namespace Serde issues (#1077)
1 parent 76af564 commit 77386f6

File tree

15 files changed

+305
-214
lines changed

15 files changed

+305
-214
lines changed

codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsProtocolUtils.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -195,17 +195,24 @@ static boolean generateUndefinedQueryInputBody(GenerationContext context, Operat
195195
* @param context The generation context.
196196
* @param shape The shape to apply the namespace attribute to, if present on it.
197197
* @param nodeName The node to apply the namespace attribute to.
198+
* @return Returns if an XML namespace attribute was written.
198199
*/
199-
static void writeXmlNamespace(GenerationContext context, Shape shape, String nodeName) {
200-
shape.getTrait(XmlNamespaceTrait.class).ifPresent(trait -> {
201-
TypeScriptWriter writer = context.getWriter();
202-
String xmlns = "xmlns";
203-
Optional<String> prefix = trait.getPrefix();
204-
if (prefix.isPresent()) {
205-
xmlns += ":" + prefix.get();
206-
}
207-
writer.write("$L.addAttribute($S, $S);", nodeName, xmlns, trait.getUri());
208-
});
200+
static boolean writeXmlNamespace(GenerationContext context, Shape shape, String nodeName) {
201+
Optional<XmlNamespaceTrait> traitOptional = shape.getTrait(XmlNamespaceTrait.class);
202+
203+
if (!traitOptional.isPresent()) {
204+
return false;
205+
}
206+
207+
XmlNamespaceTrait trait = traitOptional.get();
208+
TypeScriptWriter writer = context.getWriter();
209+
String xmlns = "xmlns";
210+
Optional<String> prefix = trait.getPrefix();
211+
if (prefix.isPresent()) {
212+
xmlns += ":" + prefix.get();
213+
}
214+
writer.write("$L.addAttribute($S, $S);", nodeName, xmlns, trait.getUri());
215+
return true;
209216
}
210217

211218
/**

codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsRestXml.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525
import software.amazon.smithy.model.shapes.MemberShape;
2626
import software.amazon.smithy.model.shapes.OperationShape;
2727
import software.amazon.smithy.model.shapes.Shape;
28+
import software.amazon.smithy.model.shapes.ShapeId;
2829
import software.amazon.smithy.model.shapes.StructureShape;
2930
import software.amazon.smithy.model.shapes.UnionShape;
3031
import software.amazon.smithy.model.traits.TimestampFormatTrait.Format;
31-
import software.amazon.smithy.model.traits.XmlNamespaceTrait;
3232
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
3333
import software.amazon.smithy.typescript.codegen.integration.HttpBindingProtocolGenerator;
3434

@@ -150,17 +150,21 @@ protected void serializeInputDocument(
150150
}
151151

152152
SymbolProvider symbolProvider = context.getSymbolProvider();
153+
ShapeId inputShapeId = documentBindings.get(0).getMember().getContainer();
153154

154155
// Start with the XML declaration.
155156
writer.write("body = \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\";");
156157

157158
writer.addImport("XmlNode", "__XmlNode", "@aws-sdk/xml-builder");
158-
writer.write("const bodyNode = new __XmlNode($S);",
159-
documentBindings.get(0).getMember().getContainer().getName());
160-
161-
// Always add @xmlNamespace value of the service to the root node, since we're
162-
// creating a wrapper node not based on a structure.
163-
AwsProtocolUtils.writeXmlNamespace(context, context.getService(), "bodyNode");
159+
writer.write("const bodyNode = new __XmlNode($S);", inputShapeId.getName());
160+
161+
// Add @xmlNamespace value of the service to the root node,
162+
// fall back to one from the input shape.
163+
boolean serviceXmlns = AwsProtocolUtils.writeXmlNamespace(context, context.getService(), "bodyNode");
164+
if (!serviceXmlns) {
165+
StructureShape inputShape = context.getModel().expectShape(inputShapeId, StructureShape.class);
166+
AwsProtocolUtils.writeXmlNamespace(context, inputShape, "bodyNode");
167+
}
164168

165169
XmlShapeSerVisitor shapeSerVisitor = new XmlShapeSerVisitor(context);
166170

@@ -207,10 +211,11 @@ protected void serializeInputPayload(
207211
// Start with the XML declaration.
208212
writer.write("body = \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\";");
209213

210-
// Add @xmlNamespace value of the service to the root structure if one doesn't
211-
// exist on the target we're serializing.
212-
if (!target.hasTrait(XmlNamespaceTrait.class)) {
213-
AwsProtocolUtils.writeXmlNamespace(context, context.getService(), "contents");
214+
// Add @xmlNamespace value of the service to the root node,
215+
// fall back to one from the payload target.
216+
boolean serviceXmlns = AwsProtocolUtils.writeXmlNamespace(context, context.getService(), "contents");
217+
if (!serviceXmlns) {
218+
AwsProtocolUtils.writeXmlNamespace(context, target, "contents");
214219
}
215220

216221
// Append the generated XML to the body.

codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/XmlMemberDeserVisitor.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import software.amazon.smithy.codegen.core.CodegenException;
1919
import software.amazon.smithy.model.shapes.BigDecimalShape;
2020
import software.amazon.smithy.model.shapes.BigIntegerShape;
21+
import software.amazon.smithy.model.shapes.BlobShape;
2122
import software.amazon.smithy.model.shapes.BooleanShape;
2223
import software.amazon.smithy.model.shapes.ByteShape;
2324
import software.amazon.smithy.model.shapes.DoubleShape;
@@ -26,6 +27,7 @@
2627
import software.amazon.smithy.model.shapes.LongShape;
2728
import software.amazon.smithy.model.shapes.Shape;
2829
import software.amazon.smithy.model.shapes.ShortShape;
30+
import software.amazon.smithy.model.shapes.StringShape;
2931
import software.amazon.smithy.model.traits.TimestampFormatTrait.Format;
3032
import software.amazon.smithy.typescript.codegen.integration.DocumentMemberDeserVisitor;
3133
import software.amazon.smithy.typescript.codegen.integration.ProtocolGenerator.GenerationContext;
@@ -49,9 +51,31 @@ final class XmlMemberDeserVisitor extends DocumentMemberDeserVisitor {
4951
super(context, dataSource, defaultTimestampFormat);
5052
}
5153

54+
@Override
55+
public String stringShape(StringShape shape) {
56+
return getSafeDataSource();
57+
}
58+
59+
/**
60+
* Provides a data source safety mechanism to handle nodes that are
61+
* expected to have only a value but were loaded from a node with
62+
* a namespace.
63+
*
64+
* @return The node's value having handled a potential namespace.
65+
*/
66+
private String getSafeDataSource() {
67+
String dataSource = getDataSource();
68+
return "((" + dataSource + "['#text'] !== undefined) ? " + dataSource + "['#text'] : " + dataSource + ")";
69+
}
70+
71+
@Override
72+
public String blobShape(BlobShape shape) {
73+
return "context.base64Decoder(" + getSafeDataSource() + ")";
74+
}
75+
5276
@Override
5377
public String booleanShape(BooleanShape shape) {
54-
return getDataSource() + " == 'true'";
78+
return getSafeDataSource() + " == 'true'";
5579
}
5680

5781
@Override
@@ -75,7 +99,7 @@ public String longShape(LongShape shape) {
7599
}
76100

77101
private String deserializeInt() {
78-
return "parseInt(" + getDataSource() + ")";
102+
return "parseInt(" + getSafeDataSource() + ")";
79103
}
80104

81105
@Override
@@ -89,7 +113,7 @@ public String doubleShape(DoubleShape shape) {
89113
}
90114

91115
private String deserializeFloat() {
92-
return "parseFloat(" + getDataSource() + ")";
116+
return "parseFloat(" + getSafeDataSource() + ")";
93117
}
94118

95119
@Override

codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/XmlShapeSerVisitor.java

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,16 @@ protected void serializeCollection(GenerationContext context, CollectionShape sh
7878
// Handle proper unwrapping of target nodes.
7979
if (serializationReturnsArray(target)) {
8080
writer.write("const container = new __XmlNode($S);", locationName);
81-
writer.openBlock("for (let index in node) {", "}",
82-
() -> writer.write("container.addChildNode(node[index]);"));
81+
writer.openBlock("for (let index in node) {", "}", () -> {
82+
writer.write("const workingNode = node[index];");
83+
// Add @xmlNamespace value of the target member.
84+
AwsProtocolUtils.writeXmlNamespace(context, memberShape, "workingNode");
85+
writer.write("container.addChildNode(workingNode);");
86+
});
8387
writer.write("collectedNodes.push(container);");
8488
} else {
89+
// Add @xmlNamespace value of the target member.
90+
AwsProtocolUtils.writeXmlNamespace(context, memberShape, "node");
8591
writer.write("collectedNodes.push(node.withName($S));", locationName);
8692
}
8793
});
@@ -115,8 +121,10 @@ protected void serializeMap(GenerationContext context, MapShape shape) {
115121
String keyName = keyMember.getTrait(XmlNameTrait.class)
116122
.map(XmlNameTrait::getValue)
117123
.orElse("key");
118-
writer.write("entryNode.addChildNode($L.withName($S));",
119-
keyTarget.accept(getMemberVisitor("key")), keyName);
124+
writer.write("const keyNode = $L.withName($S);", keyTarget.accept(getMemberVisitor("key")), keyName);
125+
// Add @xmlNamespace value of the key member.
126+
AwsProtocolUtils.writeXmlNamespace(context, keyMember, "workingNode");
127+
writer.write("entryNode.addChildNode(keyNode);");
120128

121129
// Prepare the value's node.
122130
// Use the @xmlName trait if present on the member, otherwise use "value".
@@ -130,10 +138,16 @@ protected void serializeMap(GenerationContext context, MapShape shape) {
130138
// Handle proper unwrapping of target nodes.
131139
if (serializationReturnsArray(valueTarget)) {
132140
writer.write("const container = new __XmlNode($S);", valueName);
133-
writer.openBlock("for (let index in node) {", "}",
134-
() -> writer.write("container.addChildNode(node[index]);"));
141+
writer.openBlock("for (let index in node) {", "}", () -> {
142+
writer.write("const workingNode = node[index];");
143+
// Add @xmlNamespace value of the value member.
144+
AwsProtocolUtils.writeXmlNamespace(context, valueMember, "workingNode");
145+
writer.write("container.addChildNode(workingNode);");
146+
});
135147
writer.write("entryNode.addChildNode(container);");
136148
} else {
149+
// Add @xmlNamespace value of the target member.
150+
AwsProtocolUtils.writeXmlNamespace(context, valueMember, "node");
137151
writer.write("entryNode.addChildNode(node.withName($S));", valueName);
138152
}
139153

@@ -157,9 +171,6 @@ protected void serializeStructure(GenerationContext context, StructureShape shap
157171
// Create the structure's node.
158172
writer.write("const bodyNode = new __XmlNode($S);", nodeName);
159173

160-
// Add @xmlNamespace value of the structure to the node.
161-
AwsProtocolUtils.writeXmlNamespace(context, shape, "bodyNode");
162-
163174
// Serialize every member of the structure if present.
164175
Map<String, MemberShape> members = shape.getAllMembers();
165176
members.forEach((memberName, memberShape) -> {
@@ -212,7 +223,10 @@ void serializeNamedMember(
212223
}
213224

214225
// Standard members are added as children after updating their names.
215-
writer.write("bodyNode.addChildNode($L.withName($S));", valueProvider, locationName);
226+
writer.write("const node = $L.withName($S);", valueProvider, locationName);
227+
// Add @xmlNamespace value of the target member.
228+
AwsProtocolUtils.writeXmlNamespace(context, memberShape, "node");
229+
writer.write("bodyNode.addChildNode(node);");
216230
}
217231
}
218232
}
@@ -235,6 +249,8 @@ private void serializeNamedMemberFromArray(
235249
// Prepare a containing node to hold the nodes if not flattened.
236250
if (!isFlattened) {
237251
writer.write("const containerNode = new __XmlNode($S);", locationName);
252+
// Add @xmlNamespace value of the target member.
253+
AwsProtocolUtils.writeXmlNamespace(context, memberShape, "containerNode");
238254
}
239255

240256
// Add every node to the target node.
@@ -271,9 +287,6 @@ protected void serializeUnion(GenerationContext context, UnionShape shape) {
271287
// Create the union's node.
272288
writer.write("const bodyNode = new __XmlNode($S);", nodeName);
273289

274-
// Add @xmlNamespace value of the structure to the node.
275-
AwsProtocolUtils.writeXmlNamespace(context, shape, "bodyNode");
276-
277290
// Visit over the union type, then get the right serialization for the member.
278291
writer.openBlock("$L.visit(input, {", "});", shape.getId().getName(), () -> {
279292
Map<String, MemberShape> members = shape.getAllMembers();

protocol_tests/aws-ec2/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,20 @@
3232
"@aws-crypto/sha256-browser": "^1.0.0-alpha.0",
3333
"@aws-crypto/sha256-js": "^1.0.0-alpha.0",
3434
"@aws-sdk/config-resolver": "^1.0.0-beta.0",
35-
"@aws-sdk/credential-provider-node": "^1.0.0-alpha.1",
35+
"@aws-sdk/credential-provider-node": "^1.0.0-beta.1",
3636
"@aws-sdk/fetch-http-handler": "^1.0.0-beta.0",
3737
"@aws-sdk/hash-node": "^1.0.0-beta.0",
3838
"@aws-sdk/invalid-dependency": "^1.0.0-beta.0",
3939
"@aws-sdk/middleware-content-length": "^1.0.0-beta.0",
40-
"@aws-sdk/middleware-host-header": "^1.0.0-alpha.1",
40+
"@aws-sdk/middleware-host-header": "^1.0.0-beta.1",
4141
"@aws-sdk/middleware-retry": "^1.0.0-beta.0",
4242
"@aws-sdk/middleware-serde": "^1.0.0-beta.0",
43-
"@aws-sdk/middleware-signing": "^1.0.0-alpha.1",
43+
"@aws-sdk/middleware-signing": "^1.0.0-beta.1",
4444
"@aws-sdk/middleware-stack": "^1.0.0-beta.0",
4545
"@aws-sdk/middleware-user-agent": "^1.0.0-beta.0",
4646
"@aws-sdk/node-http-handler": "^1.0.0-beta.0",
4747
"@aws-sdk/protocol-http": "^1.0.0-beta.0",
48-
"@aws-sdk/region-provider": "^1.0.0-alpha.1",
48+
"@aws-sdk/region-provider": "^1.0.0-beta.1",
4949
"@aws-sdk/smithy-client": "^1.0.0-beta.0",
5050
"@aws-sdk/stream-collector-browser": "^1.0.0-beta.0",
5151
"@aws-sdk/stream-collector-native": "^1.0.0-beta.0",

0 commit comments

Comments
 (0)