From e72717e5479b045d53c325cc61b58e6074a6c616 Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Wed, 2 Jun 2021 18:38:15 -0700 Subject: [PATCH 1/2] Support the delegation of determining the errors that can occur for an operation --- .../HttpBindingProtocolGenerator.java | 3 +- .../HttpProtocolGeneratorUtils.java | 118 ++++++++++-------- .../integration/HttpRpcProtocolGenerator.java | 3 +- .../integration/ProtocolGenerator.java | 12 ++ 4 files changed, 84 insertions(+), 52 deletions(-) diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpBindingProtocolGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpBindingProtocolGenerator.java index edafa38c0..8d477d28f 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpBindingProtocolGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpBindingProtocolGenerator.java @@ -357,7 +357,8 @@ private void generateOperationDeserializerMiddleware(GenerationContext context, goWriter.write(""); Set errorShapes = HttpProtocolGeneratorUtils.generateErrorDispatcher( - context, operation, responseType, this::writeErrorMessageCodeDeserializer); + context, operation, responseType, this::writeErrorMessageCodeDeserializer, + this::getOperationErrors); deserializingErrorShapes.addAll(errorShapes); deserializeDocumentBindingShapes.addAll(errorShapes); } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpProtocolGeneratorUtils.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpProtocolGeneratorUtils.java index 9933f5b44..30a904054 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpProtocolGeneratorUtils.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpProtocolGeneratorUtils.java @@ -16,9 +16,13 @@ package software.amazon.smithy.go.codegen.integration; import java.util.Collection; +import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; +import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; @@ -29,33 +33,36 @@ import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StructureShape; public final class HttpProtocolGeneratorUtils { - private HttpProtocolGeneratorUtils() {} + private HttpProtocolGeneratorUtils() { + } /** * Generates a function that handles error deserialization by getting the error code then * dispatching to the error-specific deserializer. - * + *

* If the error code does not map to a known error, a generic error will be returned using * the error code and error message discovered in the response. - * + *

* The default error message and code are both "UnknownError". * - * @param context The generation context. - * @param operation The operation to generate for. - * @param responseType The response type for the HTTP protocol. + * @param context The generation context. + * @param operation The operation to generate for. + * @param responseType The response type for the HTTP protocol. * @param errorMessageCodeGenerator A consumer that generates a snippet that sets the {@code errorCode} * 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 generateErrorDispatcher( + static Set generateErrorDispatcher( GenerationContext context, OperationShape operation, Symbol responseType, - Consumer errorMessageCodeGenerator + Consumer errorMessageCodeGenerator, + BiFunction> operationErrorsToShapes ) { GoWriter writer = context.getWriter(); ServiceShape service = context.getService(); @@ -68,50 +75,49 @@ static Set generateErrorDispatcher( writer.addUseImports(SmithyGoDependency.SMITHY_MIDDLEWARE); writer.openBlock("func $L(response $P, metadata *middleware.Metadata) error {", "}", errorFunctionName, responseType, () -> { - writer.addUseImports(SmithyGoDependency.BYTES); - writer.addUseImports(SmithyGoDependency.IO); - - // Copy the response body into a seekable type - writer.write("var errorBuffer bytes.Buffer"); - writer.openBlock("if _, err := io.Copy(&errorBuffer, response.Body); err != nil {", "}", () -> { - writer.write("return &smithy.DeserializationError{Err: fmt.Errorf(" - + "\"failed to copy error response body, %w\", err)}"); - }); - writer.write("errorBody := bytes.NewReader(errorBuffer.Bytes())"); - writer.write(""); - - // Set the default values for code and message. - writer.write("errorCode := \"UnknownError\""); - writer.write("errorMessage := errorCode"); - writer.write(""); - - // Dispatch to the message/code generator to try to get the specific code and message. - errorMessageCodeGenerator.accept(context); - - 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, service, protocolName); - writer.addUseImports(SmithyGoDependency.STRINGS); - writer.openBlock("case strings.EqualFold($S, errorCode):", "", errorId.getName(service), () -> { - writer.write("return $L(response, errorBody)", errorDeserFunctionName); + writer.addUseImports(SmithyGoDependency.BYTES); + writer.addUseImports(SmithyGoDependency.IO); + + // Copy the response body into a seekable type + writer.write("var errorBuffer bytes.Buffer"); + writer.openBlock("if _, err := io.Copy(&errorBuffer, response.Body); err != nil {", "}", () -> { + writer.write("return &smithy.DeserializationError{Err: fmt.Errorf(" + + "\"failed to copy error response body, %w\", err)}"); }); - }); - - // Create a generic error - writer.addUseImports(SmithyGoDependency.SMITHY); - writer.openBlock("default:", "", () -> { - writer.openBlock("genericError := &smithy.GenericAPIError{", "}", () -> { - writer.write("Code: errorCode,"); - writer.write("Message: errorMessage,"); + writer.write("errorBody := bytes.NewReader(errorBuffer.Bytes())"); + writer.write(""); + + // Set the default values for code and message. + writer.write("errorCode := \"UnknownError\""); + writer.write("errorMessage := errorCode"); + writer.write(""); + + // Dispatch to the message/code generator to try to get the specific code and message. + errorMessageCodeGenerator.accept(context); + + writer.openBlock("switch {", "}", () -> { + operationErrorsToShapes.apply(context, operation).forEach((name, errorId) -> { + StructureShape error = context.getModel().expectShape(errorId).asStructureShape().get(); + errorShapes.add(error); + String errorDeserFunctionName = ProtocolGenerator.getErrorDeserFunctionName( + error, service, protocolName); + writer.addUseImports(SmithyGoDependency.STRINGS); + writer.openBlock("case strings.EqualFold($S, errorCode):", "", name, () -> { + writer.write("return $L(response, errorBody)", errorDeserFunctionName); + }); + }); + + // Create a generic error + writer.addUseImports(SmithyGoDependency.SMITHY); + writer.openBlock("default:", "", () -> { + writer.openBlock("genericError := &smithy.GenericAPIError{", "}", () -> { + writer.write("Code: errorCode,"); + writer.write("Message: errorMessage,"); + }); + writer.write("return genericError"); + }); }); - writer.write("return genericError"); - }); - }); - }); - writer.write(""); + }).write(""); return errorShapes; } @@ -136,4 +142,16 @@ public static boolean isShapeWithResponseBindings(Model model, Shape shape, Http } return false; } + + /** + * Returns a map of error names to their {@link ShapeId}. + * + * @param context the generation context + * @param operation the operation shape to retrieve errors for + * @return map of error names to {@link ShapeId} + */ + public static Map getOperationErrors(GenerationContext context, OperationShape operation) { + return new TreeMap<>(operation.getErrors().stream() + .collect(Collectors.toMap(shapeId -> shapeId.getName(context.getService()), shapeId -> shapeId))); + } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpRpcProtocolGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpRpcProtocolGenerator.java index d0d6b8133..763a2f82f 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpRpcProtocolGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpRpcProtocolGenerator.java @@ -289,7 +289,8 @@ private void generateOperationDeserializer(GenerationContext context, OperationS writer.write(""); Set errorShapes = HttpProtocolGeneratorUtils.generateErrorDispatcher( - context, operation, responseType, this::writeErrorMessageCodeDeserializer); + context, operation, responseType, this::writeErrorMessageCodeDeserializer, + this::getOperationErrors); deserializingErrorShapes.addAll(errorShapes); deserializingDocumentShapes.addAll(errorShapes); } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java index febfe4018..8b15163be 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java @@ -17,6 +17,7 @@ import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.SymbolProvider; @@ -248,6 +249,17 @@ static String getDeserializeMiddlewareName(ShapeId operationShapeId, ServiceShap + operationShapeId.getName(service); } + /** + * Returns a map of error names to their {@link ShapeId}. + * + * @param context the generation context + * @param operation the operation shape to retrieve errors for + * @return map of error names to {@link ShapeId} + */ + default Map getOperationErrors(GenerationContext context, OperationShape operation) { + return HttpProtocolGeneratorUtils.getOperationErrors(context, operation); + } + /** * Context object used for service serialization and deserialization. */ From 8b541aad0676546422badc1b7595db9b08f20044 Mon Sep 17 00:00:00 2001 From: Sean McGrail Date: Thu, 3 Jun 2021 13:53:48 -0700 Subject: [PATCH 2/2] Implement Feedback --- .../integration/HttpProtocolGeneratorUtils.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpProtocolGeneratorUtils.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpProtocolGeneratorUtils.java index 30a904054..4e40b0f12 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpProtocolGeneratorUtils.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpProtocolGeneratorUtils.java @@ -22,7 +22,9 @@ import java.util.TreeSet; import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; +import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; @@ -146,12 +148,20 @@ public static boolean isShapeWithResponseBindings(Model model, Shape shape, Http /** * Returns a map of error names to their {@link ShapeId}. * - * @param context the generation context + * @param context the generation context * @param operation the operation shape to retrieve errors for * @return map of error names to {@link ShapeId} */ public static Map getOperationErrors(GenerationContext context, OperationShape operation) { - return new TreeMap<>(operation.getErrors().stream() - .collect(Collectors.toMap(shapeId -> shapeId.getName(context.getService()), shapeId -> shapeId))); + return operation.getErrors().stream() + .collect(Collectors.toMap( + shapeId -> shapeId.getName(context.getService()), + Function.identity(), + (x, y) -> { + if (!x.equals(y)) { + throw new CodegenException(String.format("conflicting error shape ids: %s, %s", x, y)); + } + return x; + }, TreeMap::new)); } }