-
Notifications
You must be signed in to change notification settings - Fork 603
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for AWS rest-xml protocol
This commit adds support for the `aws.rest-xml` protocol, building on top of the `HttpBindingProtocolGenerator`. It includes an additional abstract class, `RestXmlResponseProtocolGenerator`, to aid in implementation of other protocols that use XML bindings for their HTTP responses. Implementations of the `DocumentMember[Deser|Ser]Visitor` and the `DocumentShape[Deser|Ser]Visitor` have been created that handle Smithy's XML traits and their influence on protocol serde. A minor update has been made to the `XmlNode` to allow for nodes to be renamed, as the same structure may change XML node names when it is bound to different locations.
- Loading branch information
Showing
10 changed files
with
1,029 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
150 changes: 150 additions & 0 deletions
150
...cript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AwsRestXml.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
/* | ||
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
|
||
package software.amazon.smithy.aws.typescript.codegen; | ||
|
||
import java.util.List; | ||
import java.util.Set; | ||
import software.amazon.smithy.codegen.core.SymbolProvider; | ||
import software.amazon.smithy.model.knowledge.HttpBinding; | ||
import software.amazon.smithy.model.knowledge.HttpBinding.Location; | ||
import software.amazon.smithy.model.shapes.MemberShape; | ||
import software.amazon.smithy.model.shapes.OperationShape; | ||
import software.amazon.smithy.model.shapes.Shape; | ||
import software.amazon.smithy.model.shapes.StructureShape; | ||
import software.amazon.smithy.model.shapes.UnionShape; | ||
import software.amazon.smithy.model.traits.TimestampFormatTrait.Format; | ||
import software.amazon.smithy.typescript.codegen.TypeScriptWriter; | ||
|
||
/** | ||
* Handles generating the aws.rest-xml protocol for services. | ||
* | ||
* This builds on the foundations of the {@link RestXmlResponseProtocolGenerator} to handle | ||
* components of binding to HTTP responses and of the {@link HttpBindingProtocolGenerator} | ||
* to handle components of binding to HTTP requests. | ||
* | ||
* @see XmlShapeSerVisitor | ||
* @see XmlMemberSerVisitor | ||
* @see AwsProtocolUtils | ||
* @see <a href="https://awslabs.github.io/smithy/spec/http.html">Smithy HTTP protocol bindings.</a> | ||
* @see <a href="https://awslabs.github.io/smithy/spec/xml.html">Smithy XML traits.</a> | ||
*/ | ||
final class AwsRestXml extends RestXmlResponseProtocolGenerator { | ||
|
||
@Override | ||
protected String getDocumentContentType() { | ||
return "application/xml"; | ||
} | ||
|
||
@Override | ||
protected Format getDocumentTimestampFormat() { | ||
return Format.DATE_TIME; | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return "aws.rest-xml"; | ||
} | ||
|
||
@Override | ||
protected void generateDocumentBodyShapeSerializers(GenerationContext context, Set<Shape> shapes) { | ||
AwsProtocolUtils.generateDocumentBodyShapeSerde(context, shapes, new XmlShapeSerVisitor(context)); | ||
} | ||
|
||
@Override | ||
public void generateSharedComponents(GenerationContext context) { | ||
super.generateSharedComponents(context); | ||
|
||
TypeScriptWriter writer = context.getWriter(); | ||
writer.addDependency(AwsDependency.XML_BUILDER); | ||
} | ||
|
||
@Override | ||
protected void writeDefaultHeaders(GenerationContext context, OperationShape operation) { | ||
super.writeDefaultHeaders(context, operation); | ||
AwsProtocolUtils.generateUnsignedPayloadSigV4Header(context, operation); | ||
} | ||
|
||
@Override | ||
protected void serializeInputDocument( | ||
GenerationContext context, | ||
OperationShape operation, | ||
List<HttpBinding> documentBindings | ||
) { | ||
SymbolProvider symbolProvider = context.getSymbolProvider(); | ||
TypeScriptWriter writer = context.getWriter(); | ||
|
||
// Start with the XML declaration. | ||
writer.write("body = \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\";"); | ||
|
||
writer.addImport("XmlNode", "__XmlNode", "@aws-sdk/xml-builder"); | ||
writer.write("const bodyNode = new __XmlNode($S);", operation.getId().getName()); | ||
|
||
// Add @xmlNamespace value of the service to the root node. | ||
AwsProtocolUtils.writeXmlNamespace(context, context.getService(), "bodyNode"); | ||
|
||
XmlShapeSerVisitor shapeSerVisitor = new XmlShapeSerVisitor(context); | ||
|
||
for (HttpBinding binding : documentBindings) { | ||
MemberShape memberShape = binding.getMember(); | ||
// The name of the member to get from the input shape. | ||
String memberName = symbolProvider.toMemberName(memberShape); | ||
|
||
String inputLocation = "input." + memberName; | ||
writer.openBlock("if ($L !== undefined) {", "}", inputLocation, () -> { | ||
shapeSerVisitor.serializeNamedMember(context, memberName, memberShape, () -> inputLocation); | ||
}); | ||
} | ||
|
||
// Append the generated XML to the body. | ||
writer.write("body += bodyNode.toString();"); | ||
} | ||
|
||
@Override | ||
protected void serializeInputPayload( | ||
GenerationContext context, | ||
OperationShape operation, | ||
HttpBinding payloadBinding | ||
) { | ||
SymbolProvider symbolProvider = context.getSymbolProvider(); | ||
TypeScriptWriter writer = context.getWriter(); | ||
|
||
MemberShape member = payloadBinding.getMember(); | ||
String memberName = symbolProvider.toMemberName(member); | ||
|
||
writer.write("let contents: any;"); | ||
|
||
// Generate an if statement to set the body node if the member is set. | ||
writer.openBlock("if (input.$L !== undefined) {", "}", memberName, () -> { | ||
Shape target = context.getModel().expectShape(member.getTarget()); | ||
writer.write("contents = $L;", | ||
getInputValue(context, Location.PAYLOAD, "input." + memberName, member, target)); | ||
|
||
// Structure and Union payloads will serialize as XML documents via XmlNode. | ||
if (target instanceof StructureShape || target instanceof UnionShape) { | ||
// Start with the XML declaration. | ||
writer.write("body = \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\";"); | ||
// Add @xmlNamespace value of the service to the root structure. | ||
AwsProtocolUtils.writeXmlNamespace(context, context.getService(), "contents"); | ||
|
||
// Append the generated XML to the body. | ||
writer.write("body += contents.toString();"); | ||
} else { | ||
// Strings and blobs (streaming or not) will not need any modification. | ||
writer.write("body = contents;"); | ||
} | ||
}); | ||
} | ||
} |
137 changes: 137 additions & 0 deletions
137
.../java/software/amazon/smithy/aws/typescript/codegen/RestXmlResponseProtocolGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
/* | ||
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"). | ||
* You may not use this file except in compliance with the License. | ||
* A copy of the License is located at | ||
* | ||
* http://aws.amazon.com/apache2.0 | ||
* | ||
* or in the "license" file accompanying this file. This file is distributed | ||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | ||
* express or implied. See the License for the specific language governing | ||
* permissions and limitations under the License. | ||
*/ | ||
|
||
package software.amazon.smithy.aws.typescript.codegen; | ||
|
||
import java.util.List; | ||
import java.util.Set; | ||
import software.amazon.smithy.aws.traits.ServiceTrait; | ||
import software.amazon.smithy.codegen.core.SymbolProvider; | ||
import software.amazon.smithy.codegen.core.SymbolReference; | ||
import software.amazon.smithy.model.knowledge.HttpBinding; | ||
import software.amazon.smithy.model.shapes.MemberShape; | ||
import software.amazon.smithy.model.shapes.Shape; | ||
import software.amazon.smithy.typescript.codegen.TypeScriptWriter; | ||
import software.amazon.smithy.typescript.codegen.integration.HttpBindingProtocolGenerator; | ||
|
||
/** | ||
* Handles general components across the XML-document response based AWS protocols | ||
* that have HTTP bindings. It handles reading from document bodies, including | ||
* generating any functions needed for performing deserialization. | ||
* | ||
* This builds on the foundations of the {@link HttpBindingProtocolGenerator} to | ||
* handle components of binding to HTTP responses. | ||
* | ||
* @see XmlShapeDeserVisitor | ||
* @see XmlMemberDeserVisitor | ||
* @see AwsProtocolUtils | ||
* @see <a href="https://awslabs.github.io/smithy/spec/http.html">Smithy HTTP protocol bindings.</a> | ||
* @see <a href="https://awslabs.github.io/smithy/spec/xml.html">Smithy XML traits.</a> | ||
*/ | ||
abstract class RestXmlResponseProtocolGenerator extends HttpBindingProtocolGenerator { | ||
|
||
RestXmlResponseProtocolGenerator() { | ||
super(true); | ||
} | ||
|
||
@Override | ||
protected void generateDocumentBodyShapeDeserializers(GenerationContext context, Set<Shape> shapes) { | ||
AwsProtocolUtils.generateDocumentBodyShapeSerde(context, shapes, new XmlShapeDeserVisitor(context)); | ||
} | ||
|
||
@Override | ||
public void generateSharedComponents(GenerationContext context) { | ||
super.generateSharedComponents(context); | ||
|
||
TypeScriptWriter writer = context.getWriter(); | ||
|
||
// Include an XML body parser used to deserialize documents from HTTP responses. | ||
writer.addImport("SerdeContext", "__SerdeContext", "@aws-sdk/types"); | ||
writer.addDependency(AwsDependency.XML_PARSER); | ||
writer.addDependency(AwsDependency.XML_PARSER_TYPES); | ||
writer.addImport("parse", "pixlParse", "pixl-xml"); | ||
writer.openBlock("const parseBody = (streamBody: any, context: __SerdeContext): any => {", "};", () -> { | ||
writer.openBlock("return collectBodyString(streamBody, context).then(encoded => {", "});", () -> { | ||
writer.openBlock("if (encoded.length) {", "}", () -> { | ||
writer.write("return pixlParse(encoded);"); | ||
}); | ||
writer.write("return {};"); | ||
}); | ||
}); | ||
|
||
writer.write(""); | ||
|
||
// Generate a function that handles the complex rules around deserializing | ||
// an error code from a rest-xml error. | ||
SymbolReference responseType = getApplicationProtocol().getResponseType(); | ||
writer.openBlock("const loadRestXmlErrorCode = (\n" | ||
+ " output: $T,\n" | ||
+ " data: any\n" | ||
+ "): string => {", "};", responseType, () -> { | ||
// Start building the location that contains the error code. | ||
StringBuilder locationBuilder = new StringBuilder("data."); | ||
// Some services, S3 for example, don't wrap the Error object in the response. | ||
if (usesWrappedErrorResponse(context)) { | ||
locationBuilder.append("Error."); | ||
} | ||
locationBuilder.append("Code"); | ||
|
||
// Attempt to fetch the error code from the specific location. | ||
String errorCodeLocation = locationBuilder.toString(); | ||
writer.openBlock("if ($L !== undefined) {", "}", errorCodeLocation, () -> { | ||
writer.write("return $L;", errorCodeLocation); | ||
}); | ||
|
||
// Default a 404 status code to the NotFound code. | ||
writer.openBlock("if (output.statusCode == 404) {", "}", () -> writer.write("return 'NotFound';")); | ||
|
||
// Default to an UnknownError code. | ||
writer.write("return 'UnknownError';"); | ||
}); | ||
writer.write(""); | ||
} | ||
|
||
private boolean usesWrappedErrorResponse(GenerationContext context) { | ||
return context.getService().getTrait(ServiceTrait.class) | ||
.map(trait -> !trait.getSdkId().equals("S3")) | ||
.orElse(true); | ||
} | ||
|
||
@Override | ||
protected void writeErrorCodeParser(GenerationContext context) { | ||
TypeScriptWriter writer = context.getWriter(); | ||
|
||
// Outsource error code parsing since it's complex for this protocol. | ||
writer.write("errorCode = loadRestXmlErrorCode(output, parsedOutput.body);"); | ||
} | ||
|
||
@Override | ||
protected void deserializeOutputDocument( | ||
GenerationContext context, | ||
Shape operationOrError, | ||
List<HttpBinding> documentBindings | ||
) { | ||
SymbolProvider symbolProvider = context.getSymbolProvider(); | ||
XmlShapeDeserVisitor shapeDeserVisitor = new XmlShapeDeserVisitor(context); | ||
|
||
for (HttpBinding binding : documentBindings) { | ||
MemberShape memberShape = binding.getMember(); | ||
// The name of the member to get from the output shape. | ||
String memberName = symbolProvider.toMemberName(memberShape); | ||
|
||
shapeDeserVisitor.deserializeNamedStructureMember(context, memberName, memberShape, () -> "data"); | ||
} | ||
} | ||
} |
Oops, something went wrong.