Skip to content

Commit

Permalink
Merge pull request #131 from skotambkar/xml/Deser
Browse files Browse the repository at this point in the history
  • Loading branch information
skotambkar authored Aug 20, 2020
2 parents b29a2bc + eadd63b commit 7b32252
Show file tree
Hide file tree
Showing 9 changed files with 508 additions and 16 deletions.
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

0 comments on commit 7b32252

Please sign in to comment.