Skip to content
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

RestXml Deserialization support #131

Merged
merged 8 commits into from
Aug 20, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public final class SmithyGoDependency {
public static final GoDependency CRYPTORAND = stdlib("crypto/rand", "cryptorand");
public static final GoDependency TESTING = stdlib("testing");
public static final GoDependency ERRORS = stdlib("errors");
public static final GoDependency XML = stdlib("encoding/xml");

public static final GoDependency SMITHY = smithy(null, "smithy");
public static final GoDependency SMITHY_HTTP_TRANSPORT = smithy("transport/http", "smithyhttp");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
/**
* Visitor to generate deserialization functions for shapes in protocol document bodies.
*
* Visitor methods for aggregate types are final and will generate functions that dispatch
* their loading from the body to the matching abstract method.
* Visitor methods for aggregate types except maps and collections are final and will
* generate functions that dispatch their loading from the body to the matching abstract method.
*
* Visitor methods for all other types will default to not generating deserialization
* functions. This may be overwritten by downstream implementations if the protocol requires
Expand Down Expand Up @@ -472,7 +472,7 @@ public final Void documentShape(DocumentShape shape) {
* @return null
*/
@Override
public final Void listShape(ListShape shape) {
public Void listShape(ListShape shape) {
generateDeserFunction(shape, (c, s) -> deserializeCollection(c, s.asListShape().get()));
return null;
}
Expand All @@ -484,7 +484,7 @@ public final Void listShape(ListShape shape) {
* @return null
*/
@Override
public final Void mapShape(MapShape shape) {
public Void mapShape(MapShape shape) {
generateDeserFunction(shape, (c, s) -> deserializeMap(c, s.asMapShape().get()));
return null;
}
Expand All @@ -496,7 +496,7 @@ public final Void mapShape(MapShape shape) {
* @return null
*/
@Override
public final Void setShape(SetShape shape) {
public Void setShape(SetShape shape) {
generateDeserFunction(shape, (c, s) -> deserializeCollection(c, s.asSetShape().get()));
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ private void generateOperationDeserializerMiddleware(GenerationContext context,
ApplicationProtocol applicationProtocol = getApplicationProtocol();
Symbol responseType = applicationProtocol.getResponseType();
GoWriter goWriter = context.getWriter();

String errorFunctionName = ProtocolGenerator.getOperationErrorDeserFunctionName(
operation, context.getProtocolName());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private HttpProtocolGeneratorUtils() {}
* and {@code errorMessage} variables from the http response.
* @return A set of all error structure shapes for the operation that were dispatched to.
*/
static Set<StructureShape> generateErrorDispatcher(
static Set<StructureShape> generateErrorDispatcher(
GenerationContext context,
OperationShape operation,
Symbol responseType,
Expand Down Expand Up @@ -78,13 +78,14 @@ static Set<StructureShape> generateErrorDispatcher(
// Dispatch to the message/code generator to try to get the specific code and message.
errorMessageCodeGenerator.accept(context);

writer.openBlock("switch errorCode {", "}", () -> {
writer.openBlock("switch {", "}", () -> {
new TreeSet<>(operation.getErrors()).forEach(errorId -> {
StructureShape error = context.getModel().expectShape(errorId).asStructureShape().get();
errorShapes.add(error);
String errorDeserFunctionName = ProtocolGenerator.getErrorDeserFunctionName(
error, context.getProtocolName());
writer.openBlock("case $S:", "", errorId.getName(), () -> {
writer.addUseImports(SmithyGoDependency.STRINGS);
writer.openBlock("case strings.EqualFold($S, errorCode):", "", errorId.getName(), () -> {
writer.write("return $L(response, errorBody)", errorDeserFunctionName);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ private void generateOperationSerializer(GenerationContext context, OperationSha
writer.write("request.Request.URL.Path = $S", getOperationPath(context, operation));
writer.write("request.Request.Method = \"POST\"");
writer.write("httpBindingEncoder, err := httpbinding.NewEncoder(request.URL.Path, "
+ "request.URL.RawQuery, request.Header)");
+ "request.URL.RawQuery, request.Header)");
writer.openBlock("if err != nil {", "}", () -> {
writer.write("return out, metadata, &smithy.SerializationError{Err: err}");
});
Expand Down Expand Up @@ -160,16 +160,16 @@ private void writeRequestHeaders(GenerationContext context, OperationShape opera
* <li>{@code ctx: context.Context}: a type containing context and tools for type serde.</li>
* </ul>
*
* @param context The generation context.
* @param context The generation context.
* @param operation The operation being generated.
* @param writer The writer to use.
* @param writer The writer to use.
*/
protected void writeDefaultHeaders(GenerationContext context, OperationShape operation, GoWriter writer) {}

/**
* Provides the request path for the operation.
*
* @param context The generation context.
* @param context The generation context.
* @param operation The operation being generated.
* @return The path to send HTTP requests to.
*/
Expand All @@ -185,7 +185,7 @@ protected void writeDefaultHeaders(GenerationContext context, OperationShape ope
* <li>{@code ctx: context.Context}: a type containing context and tools for type serde.</li>
* </ul>
*
* @param context The generation context.
* @param context The generation context.
* @param operation The operation to serialize for.
*/
protected abstract void serializeInputDocument(GenerationContext context, OperationShape operation);
Expand All @@ -204,7 +204,7 @@ public void generateSharedDeserializerComponents(GenerationContext context) {
* {@code deserializeOutputDocument}.
*
* @param context The generation context.
* @param shapes The shapes to generate deserialization for.
* @param shapes The shapes to generate deserialization for.
*/
protected abstract void generateDocumentBodyShapeDeserializers(GenerationContext context, Set<Shape> shapes);

Expand Down Expand Up @@ -280,7 +280,7 @@ private void generateOperationDeserializer(GenerationContext context, OperationS
* <li>{@code ctx: context.Context}: a type containing context and tools for type serde.</li>
* </ul>
*
* @param context The generation context
* @param context The generation context
* @param operation The operation to deserialize for.
*/
protected abstract void deserializeOutputDocument(GenerationContext context, OperationShape operation);
Expand All @@ -306,7 +306,7 @@ private void generateErrorDeserializer(GenerationContext context, StructureShape
* </ul>
*
* @param context The generation context.
* @param shape The error shape.
* @param shape The error shape.
*/
protected abstract void deserializeError(GenerationContext context, StructureShape shape);

Expand Down
87 changes: 87 additions & 0 deletions xml/xml_decoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package xml

import (
"encoding/xml"
"fmt"
)

// NodeDecoder is a XML decoder wrapper that is responsible to decoding
// a single XML Node element and it's nested member elements. This wrapper decoder
// takes in the start element of the top level node being decoded.
type NodeDecoder struct {
Decoder *xml.Decoder
StartEl xml.StartElement
}

// WrapNodeDecoder returns an initialized XMLNodeDecoder
func WrapNodeDecoder(decoder *xml.Decoder, startEl xml.StartElement) NodeDecoder {
return NodeDecoder{
Decoder: decoder,
StartEl: startEl,
}
}

// Token on a Node Decoder returns a xml StartElement. It returns a boolean that indicates the
// a token is the node decoder's end node token; and an error which indicates any error
// that occurred while retrieving the start element
func (d NodeDecoder) Token() (t xml.StartElement, done bool, err error) {
for {
token, e := d.Decoder.Token()
if e != nil {
return t, done, e
}

// check if we reach end of the node being decoded
if el, ok := token.(xml.EndElement); ok {
return t, el == d.StartEl.End(), err
}

if t, ok := token.(xml.StartElement); ok {
return t, false, err
}

// skip token if it is a comment or preamble or empty space value due to indentation
// or if it's a value and is not expected
}

return
}

// Value provides an abstraction to retrieve char data value within an xml element.
// The method will return an error if it encounters a nested xml element instead of char data.
// This method should only be used to retrieve simple type or blob shape values as []byte.
func (d NodeDecoder) Value() (c []byte, done bool, err error) {
t, e := d.Decoder.Token()
if e != nil {
return c, done, e
}

// check if token is of type charData
if ev, ok := t.(xml.CharData); ok {
return ev, done, err
}

if ev, ok := t.(xml.EndElement); ok {
if ev == d.StartEl.End() {
return c, true, err
}
}

return c, done, fmt.Errorf("expected value for %v element, got %T type %v instead", d.StartEl.Name.Local, t, t)
}

// FetchRootElement takes in a decoder and returns the first start element within the xml body.
// This function is useful in fetching the start element of an XML response and ignore the
// comments and preamble
func FetchRootElement(decoder *xml.Decoder) (startElement xml.StartElement, err error) {
for {
t, e := decoder.Token()
if e != nil {
return startElement, e
}

if startElement, ok := t.(xml.StartElement); ok {
return startElement, err
}
}
}
Loading