parseNamespaceMap(ObjectNode settings) {
.map(node -> node.getMembers().entrySet().stream()
.collect(Collectors.toMap(
entry -> entry.getKey().getValue(),
- entry -> sanitize(entry.getValue().expectStringNode().getValue()))))
+ entry -> ServiceNameUtil.sanitizeServiceAbbreviation(
+ entry.getValue().expectStringNode().getValue()))))
.orElse(Map.of());
}
-
- private static String sanitize(String s) {
- return s.replace(" ", "").replace("-", "").replace("_", "")
- .replace("Amazon", "").replace("AWS", "").replace("/", "");
- }
}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ModelGenerator.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ModelGenerator.java
index 2cbde7ae331..b9bc248e29f 100644
--- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ModelGenerator.java
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ModelGenerator.java
@@ -4,26 +4,26 @@
*/
package com.amazonaws.util.awsclientsmithygenerator.generators.model;
-import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriter;
import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriterDelegator;
import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver.Protocol;
import com.amazonaws.util.awsclientsmithygenerator.generators.model.ShapeClassifier.ClassifiedShapes;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.renderers.EnumShapeRenderer;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.renderers.EventStreamRenderer;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.renderers.RequestRenderer;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.renderers.ResultRenderer;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.renderers.SubObjectRenderer;
import software.amazon.smithy.model.Model;
-import software.amazon.smithy.model.shapes.MemberShape;
-import software.amazon.smithy.model.shapes.Shape;
-import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.shapes.ServiceShape;
-import software.amazon.smithy.model.traits.DocumentationTrait;
+import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
/**
- * Orchestrates C++ model code generation for all classified shapes in a service.
+ * Orchestrates C++ model code generation by dispatching classified shapes
+ * to registered {@link ShapeRenderer} instances.
*
- * Dispatches to {@link EnumRenderer}, {@link MemberRenderer}, and {@link SerdeStub}
- * to produce header and source files for each model shape.
+ *
To add generation for a new shape classification (e.g., event streams),
+ * implement {@link ShapeRenderer} and register it in {@link #buildRenderers}.
*/
public class ModelGenerator {
private final Model model;
@@ -50,239 +50,35 @@ public ModelGenerator(Model model, ServiceShape service, CppWriterDelegator writ
public void generateAll() {
ClassifiedShapes classified = ShapeClassifier.classify(model, service);
+ List renderers = buildRenderers(classified);
- // Generate enum headers + sources
- for (Shape enumShape : classified.enums()) {
- generateEnumHeader(enumShape);
- generateEnumSource(enumShape);
+ for (ShapeRenderer renderer : renderers) {
+ renderer.render(writerDelegator);
}
-
- // Generate sub-object headers + sources
- for (Shape shape : classified.subObjects()) {
- if (shape.isStructureShape()) {
- generateStructureHeader(shape.asStructureShape().get(), null);
- generateStructureSource(shape.asStructureShape().get(), null);
- }
- }
-
- // Generate request headers + sources (Input -> Request)
- for (StructureShape request : classified.requests()) {
- String cppName = mapRequestName(request.getId().getName());
- generateStructureHeader(request, cppName);
- generateStructureSource(request, cppName);
- }
-
- // Generate result headers + sources (Output -> Result)
- for (StructureShape result : classified.results()) {
- String cppName = mapResultName(result.getId().getName());
- generateStructureHeader(result, cppName);
- generateStructureSource(result, cppName);
- }
- }
-
- /**
- * Maps a Smithy input shape name to the C++ Request class name.
- * e.g., "AddTagsToStreamInput" -> "AddTagsToStreamRequest"
- */
- private String mapRequestName(String smithyName) {
- if (smithyName.endsWith("Input")) {
- return smithyName.substring(0, smithyName.length() - "Input".length()) + "Request";
- }
- return smithyName;
- }
-
- /**
- * Maps a Smithy output shape name to the C++ Result class name.
- * e.g., "DescribeLimitsOutput" -> "DescribeLimitsResult"
- */
- private String mapResultName(String smithyName) {
- if (smithyName.endsWith("Output")) {
- return smithyName.substring(0, smithyName.length() - "Output".length()) + "Result";
- }
- return smithyName;
- }
-
- private void generateEnumHeader(Shape enumShape) {
- String fileName = "include/aws/" + smithyServiceName + "/model/" + enumShape.getId().getName() + ".h";
- writerDelegator.useFileWriter(fileName, writer ->
- EnumRenderer.renderHeader(writer, enumShape, namespace, exportMacro, smithyServiceName));
- }
-
- private void generateEnumSource(Shape enumShape) {
- String fileName = "source/model/" + enumShape.getId().getName() + ".cpp";
- writerDelegator.useFileWriter(fileName, writer ->
- EnumRenderer.renderSource(writer, enumShape, namespace, exportMacro, smithyServiceName));
- }
-
- private void generateStructureHeader(StructureShape shape, String classNameOverride) {
- String className = classNameOverride != null ? classNameOverride : shape.getId().getName();
- String fileName = "include/aws/" + smithyServiceName + "/model/" + className + ".h";
- writerDelegator.useFileWriter(fileName, writer -> {
- // Copyright + pragma
- writer.write("/**");
- writer.write(" * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.");
- writer.write(" * SPDX-License-Identifier: Apache-2.0.");
- writer.write(" */");
- writer.write("");
- writer.write("#pragma once");
-
- // Includes
- renderIncludes(writer, shape);
-
- // Include unless ALL members are primitives (matches C2J behavior)
- boolean allPrimitive = shape.getAllMembers().values().stream()
- .map(m -> model.expectShape(m.getTarget()))
- .allMatch(ModelGenerator::isPrimitive);
- if (!allPrimitive) {
- writer.write("");
- writer.write("#include ");
- }
- writer.write("");
-
- // Open namespace Aws (contains both forward decls and service namespace)
- writer.writeNamespaceOpen("Aws");
-
- // Forward declarations for serde types
- renderForwardDeclarations(writer);
-
- // Service namespace (still inside namespace Aws)
- writer.writeNamespaceOpen(namespace);
- writer.writeNamespaceOpen("Model");
- writer.write("");
-
- // Class-level documentation comment
- renderClassDocComment(writer, shape);
-
- // Class declaration
- writer.openBlock("class $L {", "};", className, () -> {
- writer.write("public:");
-
- // Serde declarations
- SerdeStub.renderHeaderDeclarations(writer, protocol, exportMacro, className);
- writer.write("");
-
- // Member accessors
- MemberRenderer.renderPublicSection(writer, shape, model, exportMacro, className);
-
- // Private section
- writer.dedent();
- writer.write("private:");
- writer.indent();
- MemberRenderer.renderPrivateSection(writer, shape, model);
- });
- writer.write("");
-
- // Namespace close
- writer.writeNamespaceClose("Model");
- writer.writeNamespaceClose(namespace);
- writer.writeNamespaceClose("Aws");
- });
}
- private void generateStructureSource(StructureShape shape, String classNameOverride) {
- String className = classNameOverride != null ? classNameOverride : shape.getId().getName();
- String fileName = "source/model/" + className + ".cpp";
- writerDelegator.useFileWriter(fileName, writer -> {
- writer.write("/**");
- writer.write(" * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.");
- writer.write(" * SPDX-License-Identifier: Apache-2.0.");
- writer.write(" */");
- writer.write("");
-
- // Includes
- if (protocol.isJsonLike()) {
- writer.write("#include ");
- } else {
- writer.write("#include ");
- }
- writer.write("#include ", smithyServiceName, className);
- writer.write("");
- writer.write("#include ");
- writer.write("");
+ private List buildRenderers(ClassifiedShapes classified) {
+ List renderers = new ArrayList<>();
- // Using declarations
- if (protocol.isJsonLike()) {
- writer.write("using namespace Aws::Utils::Json;");
- writer.write("using namespace Aws::Utils;");
- } else {
- writer.write("using namespace Aws::Utils::Xml;");
- writer.write("using namespace Aws::Utils;");
- }
- writer.write("");
+ renderers.add(new EnumShapeRenderer(
+ classified.enums(), namespace, exportMacro, smithyServiceName));
- // Namespace open
- writer.writeNamespaceOpen("Aws");
- writer.writeNamespaceOpen(namespace);
- writer.writeNamespaceOpen("Model");
- writer.write("");
+ renderers.add(new SubObjectRenderer(
+ classified.subObjects(), model, service, protocol,
+ namespace, exportMacro, serviceName, smithyServiceName));
- // Serde implementation
- SerdeStub.renderSourceImplementation(writer, shape, model, protocol,
- className, serviceName, smithyServiceName);
- writer.write("");
-
- // Namespace close
- writer.writeNamespaceClose("Model");
- writer.writeNamespaceClose(namespace);
- writer.writeNamespaceClose("Aws");
- });
- }
+ renderers.add(new RequestRenderer(
+ classified.requests(), model, service, protocol,
+ namespace, exportMacro, smithyServiceName));
- private void renderIncludes(CppWriter writer, StructureShape shape) {
- Set includes = new TreeSet<>(); // sorted for deterministic output
- includes.add("");
+ renderers.add(new ResultRenderer(
+ classified.results(), model, service, protocol,
+ namespace, exportMacro, smithyServiceName));
- // Gather includes for all member types
- List memberIncludes = CppTypeMapper.getIncludesForShape(shape, model, smithyServiceName);
- includes.addAll(memberIncludes);
-
- for (String include : includes) {
- writer.write("#include $L", include);
- }
- }
-
- private void renderForwardDeclarations(CppWriter writer) {
- if (protocol.isJsonLike()) {
- writer.writeNamespaceOpen("Utils");
- writer.writeNamespaceOpen("Json");
- writer.write("class JsonValue;");
- writer.write("class JsonView;");
- writer.writeNamespaceClose("Json");
- writer.writeNamespaceClose("Utils");
- } else {
- writer.writeNamespaceOpen("Utils");
- writer.writeNamespaceOpen("Xml");
- writer.write("class XmlNode;");
- writer.writeNamespaceClose("Xml");
- writer.writeNamespaceClose("Utils");
- }
- }
-
- private static boolean isPrimitive(Shape shape) {
- return shape.isIntegerShape() || shape.isLongShape()
- || shape.isBooleanShape() || shape.isDoubleShape()
- || shape.isFloatShape();
- }
-
- private static boolean isPrimitiveOrEnum(Shape shape) {
- return isPrimitive(shape) || shape.isEnumShape();
- }
+ renderers.add(new EventStreamRenderer(
+ classified.eventStreamHandlers(), model, service, protocol,
+ namespace, exportMacro, smithyServiceName));
- private void renderClassDocComment(CppWriter writer, StructureShape shape) {
- shape.getTrait(DocumentationTrait.class).ifPresent(doc -> {
- String docText = doc.getValue();
- // Add "See Also" link consistent with C2J output
- String version = service.getVersion();
- String seeAlso = String.format(
- "See Also:
AWS\n" +
- "API Reference
",
- smithyServiceName, version, shape.getId().getName());
- writer.write("/**");
- for (String line : (docText + seeAlso).split("\n")) {
- writer.write(" $L", "* " + line);
- }
- writer.write(" */");
- });
+ return renderers;
}
}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ModelTransform.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ModelTransform.java
new file mode 100644
index 00000000000..396fb3be6bf
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ModelTransform.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model;
+
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.ServiceShape;
+
+/**
+ * A model-to-model transform applied before code generation.
+ *
+ * Transforms run in sequence. Each receives the model produced by the previous
+ * transform (or the original model for the first in the chain). Service-level
+ * transforms (e.g., S3-specific shape mutations) implement this interface and are
+ * registered in the pipeline.
+ */
+@FunctionalInterface
+public interface ModelTransform {
+
+ /**
+ * Applies this transform to the model.
+ *
+ * @param model the current model (may have been modified by earlier transforms)
+ * @param service the service shape being generated
+ * @return the transformed model (may be the same instance if no changes needed)
+ */
+ Model apply(Model model, ServiceShape service);
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/SerdeStub.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/SerdeStub.java
index d17bdf26de2..0efd09b7dc2 100644
--- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/SerdeStub.java
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/SerdeStub.java
@@ -20,20 +20,44 @@ private SerdeStub() {
public static void renderHeaderDeclarations(CppWriter writer, Protocol protocol,
String exportMacro, String className) {
+ renderHeaderDeclarations(writer, protocol, exportMacro, className, null);
+ }
+
+ /**
+ * Emits the protocol-specific serde method declarations for a class.
+ *
+ * @param beforeSerializeMethod optional hook run after the (de)serialization constructor
+ * and {@code operator=} declarations but before the serialize method
+ * ({@code Jsonize}/{@code AddToNode}/{@code OutputToStream}). Used to inject
+ * additional constructors (e.g. the event-stream initial-response header ctor)
+ * at the mainline position. Ignored when {@code null}.
+ */
+ public static void renderHeaderDeclarations(CppWriter writer, Protocol protocol,
+ String exportMacro, String className,
+ Runnable beforeSerializeMethod) {
if (protocol.isJsonLike()) {
writer.write("$L $L() = default;", exportMacro, className);
writer.write("$L $L(Aws::Utils::Json::JsonView jsonValue);", exportMacro, className);
writer.write("$L $L& operator=(Aws::Utils::Json::JsonView jsonValue);", exportMacro, className);
+ if (beforeSerializeMethod != null) {
+ beforeSerializeMethod.run();
+ }
writer.write("$L Aws::Utils::Json::JsonValue Jsonize() const;", exportMacro);
} else if (protocol == Protocol.REST_XML) {
writer.write("$L $L() = default;", exportMacro, className);
writer.write("$L $L(const Aws::Utils::Xml::XmlNode& xmlNode);", exportMacro, className);
writer.write("$L $L& operator=(const Aws::Utils::Xml::XmlNode& xmlNode);", exportMacro, className);
+ if (beforeSerializeMethod != null) {
+ beforeSerializeMethod.run();
+ }
writer.write("$L void AddToNode(Aws::Utils::Xml::XmlNode& parentNode) const;", exportMacro);
} else if (protocol == Protocol.QUERY_XML || protocol == Protocol.EC2) {
writer.write("$L $L() = default;", exportMacro, className);
writer.write("$L $L(const Aws::Utils::Xml::XmlNode& xmlNode);", exportMacro, className);
writer.write("$L $L& operator=(const Aws::Utils::Xml::XmlNode& xmlNode);", exportMacro, className);
+ if (beforeSerializeMethod != null) {
+ beforeSerializeMethod.run();
+ }
writer.write("$L void OutputToStream(Aws::OStream& ostream, const char* location, unsigned index, const char* locationValue) const;", exportMacro);
writer.write("$L void OutputToStream(Aws::OStream& ostream, const char* location) const;", exportMacro);
} else {
@@ -56,6 +80,46 @@ public static void renderSourceImplementation(CppWriter writer, StructureShape s
}
}
+ /**
+ * Emits the empty per-protocol serde source bodies for a class that has no backing
+ * StructureShape (event stream union / initial response). Mirrors
+ * {@link #renderSourceImplementation} but keyed only on the class name.
+ */
+ public static void renderSerdeSourceStub(CppWriter writer, Protocol protocol, String className) {
+ if (protocol.isJsonLike()) {
+ renderJsonStub(writer, className);
+ } else if (protocol == Protocol.REST_XML) {
+ renderXmlStub(writer, className);
+ } else if (protocol == Protocol.QUERY_XML || protocol == Protocol.EC2) {
+ renderQueryXmlStub(writer, className);
+ } else {
+ throw new UnsupportedOperationException("Unsupported protocol for serde source stub: " + protocol);
+ }
+ }
+
+ /**
+ * Emits a protocol-agnostic stub for an event-stream event case body: a TODO marker
+ * and a minimal, compilable callback invocation with a default-constructed event.
+ * No protocol-specific deserialization is emitted.
+ *
+ * @param writer the CppWriter
+ * @param eventShapeName the C++ event shape type (e.g. "SubscribeToShardEvent")
+ * @param callbackMember the handler callback member (e.g. "m_onSubscribeToShardEvent")
+ */
+ public static void renderEventPayloadDecodeStub(CppWriter writer, String eventShapeName,
+ String callbackMember) {
+ writer.write("// TODO: protocol-specific event payload deserialization");
+ writer.write("$L($L{});", callbackMember, eventShapeName);
+ }
+
+ /**
+ * Emits a protocol-agnostic stub in place of the protocol-specific error-payload parse.
+ * The header-derived error code/message still flow to MarshallError at the call site.
+ */
+ public static void renderErrorPayloadParseStub(CppWriter writer) {
+ writer.write("// TODO: protocol-specific error payload deserialization");
+ }
+
private static void renderJsonStub(CppWriter writer, String className) {
writer.write("$L::$L(JsonView jsonValue) { *this = jsonValue; }", className, className);
writer.write("");
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ShapeClassifier.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ShapeClassifier.java
index bb3ab03b0f9..df118be1ac0 100644
--- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ShapeClassifier.java
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ShapeClassifier.java
@@ -8,10 +8,22 @@
import com.amazonaws.util.awsclientsmithygenerator.generators.model.transforms.GlobalTransforms;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.TopDownIndex;
-import software.amazon.smithy.model.shapes.*;
-import software.amazon.smithy.model.traits.*;
-
-import java.util.*;
+import software.amazon.smithy.model.shapes.EnumShape;
+import software.amazon.smithy.model.shapes.MemberShape;
+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;
+import software.amazon.smithy.model.shapes.UnionShape;
+import software.amazon.smithy.model.traits.EnumTrait;
+import software.amazon.smithy.model.traits.ErrorTrait;
+import software.amazon.smithy.model.traits.StreamingTrait;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -31,6 +43,22 @@ public enum Classification {
EVENT_STREAM_HANDLER, BLOB_PAYLOAD_EVENT, OUTGOING_EVENT_STREAM, SKIP
}
+ /**
+ * Associates an operation input shape with its parent operation.
+ *
+ * @param shape the request structure shape
+ * @param operation the operation shape that uses this as input
+ */
+ public record RequestInfo(StructureShape shape, OperationShape operation) {}
+
+ /**
+ * Associates an operation output shape with its parent operation.
+ *
+ * @param shape the result structure shape
+ * @param operation the operation shape that uses this as output
+ */
+ public record ResultInfo(StructureShape shape, OperationShape operation) {}
+
/**
* Metadata about an operation whose output contains an event stream.
*
@@ -43,8 +71,8 @@ public record EventStreamInfo(String operationName, StructureShape requestShape,
/**
* The full classification result for a service.
*
- * @param requests operation input shapes
- * @param results operation output shapes (excluding event-stream-bearing)
+ * @param requests operation input shapes with their operations
+ * @param results operation output shapes with their operations (excluding event-stream-bearing)
* @param subObjects remaining StructureShape/UnionShape reachable from operations
* @param enums EnumShape or StringShape with @enum trait
* @param eventStreamHandlers operation + request/result shape tuples for event stream handlers
@@ -52,8 +80,8 @@ public record EventStreamInfo(String operationName, StructureShape requestShape,
* @param outgoingEventStreams outgoing event stream shapes (header only)
*/
public record ClassifiedShapes(
- List requests,
- List results,
+ List requests,
+ List results,
List subObjects,
List enums,
List eventStreamHandlers,
@@ -78,8 +106,8 @@ public static ClassifiedShapes classify(Model model, ServiceShape service) {
Set inputShapeIds = new HashSet<>();
Set outputShapeIds = new HashSet<>();
- List requests = new ArrayList<>();
- List results = new ArrayList<>();
+ List requests = new ArrayList<>();
+ List results = new ArrayList<>();
List subObjects = new ArrayList<>();
List enums = new ArrayList<>();
List eventStreamHandlers = new ArrayList<>();
@@ -91,7 +119,7 @@ public static ClassifiedShapes classify(Model model, ServiceShape service) {
op.getInput().ifPresent(id -> {
inputShapeIds.add(id);
model.getShape(id).flatMap(Shape::asStructureShape).ifPresent(s -> {
- requests.add(s);
+ requests.add(new RequestInfo(s, op));
// Check if operation has event-stream-bearing result
boolean resultHasEventStream = op.getOutput()
@@ -109,7 +137,7 @@ public static ClassifiedShapes classify(Model model, ServiceShape service) {
outputShapeIds.add(id);
model.getShape(id).flatMap(Shape::asStructureShape).ifPresent(s -> {
if (!hasEventStreamMembers(s, model)) {
- results.add(s);
+ results.add(new ResultInfo(s, op));
}
// If has event stream members, result is skipped (handler generated instead)
});
@@ -164,4 +192,38 @@ private static boolean hasEventStreamMembers(StructureShape shape, Model model)
return target.isUnionShape() && target.hasTrait(StreamingTrait.class);
});
}
+
+ /**
+ * Returns true if the operation's output structure has an event stream (a member
+ * targeting a {@code @streaming} union). This is the response-side (read) test.
+ *
+ * @param op the operation
+ * @param model the Smithy model
+ * @return true if the operation produces an event stream response
+ */
+ public static boolean isEventStreamResponseOperation(OperationShape op, Model model) {
+ return op.getOutput()
+ .flatMap(model::getShape)
+ .flatMap(Shape::asStructureShape)
+ .map(out -> hasEventStreamMembers(out, model))
+ .orElse(false);
+ }
+
+ /**
+ * Returns true if the operation's input structure has an event stream (a member
+ * targeting a {@code @streaming} union). This is the request-side (write) test,
+ * used only to flag bidirectional requests; input-stream encoder generation is
+ * out of scope.
+ *
+ * @param op the operation
+ * @param model the Smithy model
+ * @return true if the operation consumes an event stream request
+ */
+ public static boolean isEventStreamRequestOperation(OperationShape op, Model model) {
+ return op.getInput()
+ .flatMap(model::getShape)
+ .flatMap(Shape::asStructureShape)
+ .map(in -> hasEventStreamMembers(in, model))
+ .orElse(false);
+ }
}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ShapeRenderer.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ShapeRenderer.java
new file mode 100644
index 00000000000..47ae2d878c9
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ShapeRenderer.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriterDelegator;
+
+/**
+ * Strategy interface for rendering C++ code for a specific shape classification.
+ *
+ * Each implementation handles one classification bucket (enum, sub-object, request,
+ * result, event stream, etc.). The {@link ModelGenerator} dispatches classified shapes
+ * to the appropriate renderer.
+ *
+ *
To add a new classification (e.g., event stream), implement this interface and
+ * register the renderer in {@link ModelGenerator}.
+ */
+public interface ShapeRenderer {
+
+ /**
+ * Renders all C++ files (headers and sources) for the shapes this renderer handles.
+ *
+ * @param writerDelegator the file writer to emit generated code through
+ */
+ void render(CppWriterDelegator writerDelegator);
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/TransformPipeline.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/TransformPipeline.java
new file mode 100644
index 00000000000..4259807bbd1
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/TransformPipeline.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model;
+
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.ServiceShape;
+
+import java.util.List;
+
+/**
+ * An ordered sequence of {@link ModelTransform}s applied before code generation.
+ *
+ *
Transforms execute in list order. The output of each becomes the input to the next.
+ * An empty pipeline returns the input model unchanged.
+ */
+public final class TransformPipeline {
+
+ private final List transforms;
+
+ public TransformPipeline(List transforms) {
+ this.transforms = List.copyOf(transforms);
+ }
+
+ /**
+ * Applies all transforms in order and returns the final model.
+ *
+ * @param model the original model
+ * @param service the service shape being generated
+ * @return the model after all transforms have been applied
+ */
+ public Model apply(Model model, ServiceShape service) {
+ Model current = model;
+ for (ModelTransform transform : transforms) {
+ current = transform.apply(current, service);
+ }
+ return current;
+ }
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/EnumShapeRenderer.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/EnumShapeRenderer.java
new file mode 100644
index 00000000000..5e187dcce75
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/EnumShapeRenderer.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model.renderers;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriterDelegator;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.EnumRenderer;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ShapeRenderer;
+import software.amazon.smithy.model.shapes.Shape;
+
+import java.util.List;
+
+/**
+ * Renders C++ enum class headers and mapper source files for all classified enum shapes.
+ */
+public final class EnumShapeRenderer implements ShapeRenderer {
+
+ private final List enumShapes;
+ private final String namespace;
+ private final String exportMacro;
+ private final String smithyServiceName;
+
+ public EnumShapeRenderer(List enumShapes, String namespace,
+ String exportMacro, String smithyServiceName) {
+ this.enumShapes = enumShapes;
+ this.namespace = namespace;
+ this.exportMacro = exportMacro;
+ this.smithyServiceName = smithyServiceName;
+ }
+
+ @Override
+ public void render(CppWriterDelegator writerDelegator) {
+ for (Shape enumShape : enumShapes) {
+ String name = enumShape.getId().getName();
+ String headerFile = "include/aws/" + smithyServiceName + "/model/" + name + ".h";
+ writerDelegator.useFileWriter(headerFile, writer ->
+ EnumRenderer.renderHeader(writer, enumShape, namespace, exportMacro, smithyServiceName));
+
+ String sourceFile = "source/model/" + name + ".cpp";
+ writerDelegator.useFileWriter(sourceFile, writer ->
+ EnumRenderer.renderSource(writer, enumShape, namespace, exportMacro, smithyServiceName));
+ }
+ }
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/EventStreamRenderer.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/EventStreamRenderer.java
new file mode 100644
index 00000000000..52567d284e7
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/EventStreamRenderer.java
@@ -0,0 +1,590 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model.renderers;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriter;
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriterDelegator;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.CppTypeMapper;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.MemberRenderer;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver.Protocol;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.SerdeStub;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ShapeClassifier.EventStreamInfo;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ShapeRenderer;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.MemberShape;
+import software.amazon.smithy.model.shapes.ServiceShape;
+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.DocumentationTrait;
+import software.amazon.smithy.model.traits.ErrorTrait;
+import software.amazon.smithy.model.traits.StreamingTrait;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Renders C++ event stream artifacts for response-side (simplex) streaming operations:
+ * the handler, initial response, and event stream union. Driven by the classifier's
+ * {@link EventStreamInfo} list. Event structure shapes themselves are generated
+ * elsewhere (as reachable sub-objects) and only referenced here.
+ *
+ * No protocol-specific serialization is emitted; payload (de)serialization points
+ * are protocol-agnostic TODO stubs via {@link SerdeStub}.
+ */
+public final class EventStreamRenderer implements ShapeRenderer {
+
+ private final List eventStreams;
+ private final Model model;
+ private final ServiceShape service;
+ private final Protocol protocol;
+ private final String namespace;
+ private final String exportMacro;
+ private final String smithyServiceName;
+
+ public EventStreamRenderer(List eventStreams, Model model, ServiceShape service,
+ Protocol protocol, String namespace, String exportMacro,
+ String smithyServiceName) {
+ this.eventStreams = eventStreams;
+ this.model = model;
+ this.service = service;
+ this.protocol = protocol;
+ this.namespace = namespace;
+ this.exportMacro = exportMacro;
+ this.smithyServiceName = smithyServiceName;
+ }
+
+ @Override
+ public void render(CppWriterDelegator writerDelegator) {
+ for (EventStreamInfo info : eventStreams) {
+ Optional streamingUnion = findStreamingUnion(info.resultShape());
+ if (streamingUnion.isEmpty()) {
+ continue;
+ }
+ UnionShape union = streamingUnion.get();
+ List events = new ArrayList<>();
+ List exceptions = new ArrayList<>();
+ partitionMembers(union, events, exceptions);
+
+ renderHandlerHeader(writerDelegator, info.operationName(), events);
+ renderHandlerSource(writerDelegator, info.operationName(), events);
+ renderInitialResponse(writerDelegator, info.operationName());
+ renderEventStreamUnion(writerDelegator, info.operationName(), union, events, exceptions);
+ }
+ }
+
+ /** Finds the @streaming union targeted by a member of the result structure, if any. */
+ private Optional findStreamingUnion(StructureShape resultShape) {
+ for (MemberShape member : resultShape.getAllMembers().values()) {
+ Shape target = model.expectShape(member.getTarget());
+ if (target.isUnionShape() && target.hasTrait(StreamingTrait.class)) {
+ return target.asUnionShape();
+ }
+ }
+ return Optional.empty();
+ }
+
+ /** Splits union members into events (non-exception) and exceptions, preserving order. */
+ private void partitionMembers(UnionShape union, List events, List exceptions) {
+ for (MemberShape member : union.getAllMembers().values()) {
+ Shape target = model.expectShape(member.getTarget());
+ if (target.hasTrait(ErrorTrait.class)) {
+ exceptions.add(member);
+ } else {
+ events.add(member);
+ }
+ }
+ }
+
+ /** The C++ event type identifier (event shape name), e.g. "AlphaEvent". */
+ private String eventShapeName(MemberShape member) {
+ return member.getTarget().getName();
+ }
+
+ /** The wire member key, e.g. "alpha". */
+ private String wireKey(MemberShape member) {
+ return member.getMemberName();
+ }
+
+ /** The EventType enum constant: wire member key uppercased, e.g. "ALPHA". */
+ private String enumConstant(MemberShape member) {
+ return wireKey(member).toUpperCase();
+ }
+
+ // ---- Handler ------------------------------------------------------------
+
+ private void renderHandlerHeader(CppWriterDelegator writerDelegator, String opName,
+ List events) {
+ String className = opName + "Handler";
+ String fileName = "include/aws/" + smithyServiceName + "/model/" + className + ".h";
+ writerDelegator.useFileWriter(fileName, writer -> {
+ writer.write("#pragma once");
+ writer.write("#include ");
+ writer.write("#include ");
+ writer.write("#include ");
+ writer.write("#include ", smithyServiceName, namespace);
+ writer.write("#include ", smithyServiceName, namespace);
+ writer.write("#include ", smithyServiceName, opName);
+ for (MemberShape event : events) {
+ writer.write("#include ", smithyServiceName, eventShapeName(event));
+ }
+ writer.write("");
+ writer.writeNamespaceOpen("Aws");
+ writer.writeNamespaceOpen(namespace);
+ writer.writeNamespaceOpen("Model");
+
+ // EventType enum
+ StringBuilder enumBody = new StringBuilder("enum class ")
+ .append(opName).append("EventType { INITIAL_RESPONSE, ");
+ for (MemberShape event : events) {
+ enumBody.append(enumConstant(event)).append(", ");
+ }
+ enumBody.append("UNKNOWN };");
+ writer.write("$L", enumBody.toString());
+ writer.write("");
+
+ writer.openBlock("class $1L : public Aws::Utils::Event::EventStreamHandler {", "};", className, () -> {
+ // Callback typedefs
+ writer.write("typedef std::function $1LInitialResponseCallback;", opName);
+ writer.write("typedef std::function $1LInitialResponseCallbackEx;", opName);
+ for (MemberShape event : events) {
+ String ev = eventShapeName(event);
+ writer.write("typedef std::function $1LCallback;", ev);
+ }
+ writer.write("typedef std::function& error)> ErrorCallback;", namespace);
+ writer.write("");
+ writer.dedent();
+ writer.write("public:");
+ writer.indent();
+ writer.write("$1L $2L();", exportMacro, className);
+ writer.write("$1L $2L& operator=(const $2L&) = default;", exportMacro, className);
+ writer.write("$1L $2L(const $2L&) = default;", exportMacro, className);
+ writer.write("");
+ writer.write("$1L virtual void OnEvent() override;", exportMacro);
+ writer.write("");
+ writer.write("///@{");
+ writer.write("/**");
+ writer.write(" * Sets an initial response callback. This callback gets called on the initial $1L Operation response.", opName);
+ writer.write(" * This can be either \"initial-response\" decoded event frame or decoded HTTP headers received on connection.");
+ writer.write(" * This callback may get called more than once (i.e. on connection headers received and then on the initial-response event received).");
+ writer.write(" * @param callback");
+ writer.write(" */");
+ writer.write("inline void SetInitialResponseCallbackEx(const $1LInitialResponseCallbackEx& callback) { m_onInitialResponse = callback; }", opName);
+ writer.write("/**");
+ writer.write(" * Sets an initial response callback (a legacy one that does not distinguish whether response originates from headers or from the event).");
+ writer.write(" */");
+ writer.openBlock("inline void SetInitialResponseCallback(const $1LInitialResponseCallback& noArgCallback) {", "}", opName, () -> {
+ writer.write("m_onInitialResponse = [noArgCallback](const $1LInitialResponse& rs, const Utils::Event::InitialResponseType) {", opName);
+ writer.write("return noArgCallback(rs);");
+ writer.write("};");
+ });
+ writer.write("///@}");
+ for (MemberShape event : events) {
+ String ev = eventShapeName(event);
+ writer.write("inline void Set$1LCallback(const $1LCallback& callback) { m_on$1L = callback; }", ev);
+ }
+ writer.write("inline void SetOnErrorCallback(const ErrorCallback& callback) { m_onError = callback; }");
+ writer.write("");
+ writer.write("inline $1LInitialResponseCallbackEx& GetInitialResponseCallbackEx() { return m_onInitialResponse; }", opName);
+ writer.write("");
+ writer.dedent();
+ writer.write("private:");
+ writer.indent();
+ writer.write("$1L void HandleEventInMessage();", exportMacro);
+ writer.write("$1L void HandleErrorInMessage();", exportMacro);
+ writer.write("$1L void MarshallError(const Aws::String& errorCode, const Aws::String& errorMessage);", exportMacro);
+ writer.write("");
+ writer.write("$1LInitialResponseCallbackEx m_onInitialResponse;", opName);
+ for (MemberShape event : events) {
+ String ev = eventShapeName(event);
+ writer.write("$1LCallback m_on$1L;", ev);
+ }
+ writer.write("ErrorCallback m_onError;");
+ });
+ writer.write("");
+
+ writer.writeNamespaceOpen(opName + "EventMapper");
+ writer.write("$1L $2LEventType Get$2LEventTypeForName(const Aws::String& name);", exportMacro, opName);
+ writer.write("");
+ writer.write("$1L Aws::String GetNameFor$2LEventType($2LEventType value);", exportMacro, opName);
+ writer.writeNamespaceClose(opName + "EventMapper");
+
+ writer.writeNamespaceClose("Model");
+ writer.writeNamespaceClose(namespace);
+ writer.writeNamespaceClose("Aws");
+ });
+ }
+
+ private void renderHandlerSource(CppWriterDelegator writerDelegator, String opName,
+ List events) {
+ String className = opName + "Handler";
+ String fileName = "source/model/" + className + ".cpp";
+ String tag = opName.toUpperCase() + "_HANDLER_CLASS_TAG";
+ writerDelegator.useFileWriter(fileName, writer -> {
+ writer.write("#include ");
+ writer.write("#include ");
+ writer.write("#include ");
+ writer.write("#include ", smithyServiceName, namespace);
+ writer.write("#include ", smithyServiceName, opName);
+ writer.write("");
+ writer.write("using namespace Aws::$1L::Model;", namespace);
+ writer.write("using namespace Aws::Utils::Event;");
+ writer.write("");
+ writer.write("AWS_CORE_API extern const char MESSAGE_LOWER_CASE[];");
+ writer.write("AWS_CORE_API extern const char MESSAGE_CAMEL_CASE[];");
+ writer.write("");
+ writer.writeNamespaceOpen("Aws");
+ writer.writeNamespaceOpen(namespace);
+ writer.writeNamespaceOpen("Model");
+ writer.write("using namespace Aws::Client;");
+ writer.write("");
+ writer.write("static const char $1L[] = \"$2LHandler\";", tag, opName);
+ writer.write("");
+
+ // Constructor
+ writer.openBlock("$1L::$1L() : EventStreamHandler() {", "}", className, () -> {
+ writer.openBlock("m_onInitialResponse = [&](const $1LInitialResponse&, const Utils::Event::InitialResponseType eventType) {", "};", opName, () -> {
+ writer.write("AWS_LOGSTREAM_TRACE($1L, \"$2L initial response received from \" << (eventType == Utils::Event::InitialResponseType::ON_EVENT ? \"event\" : \"http headers\"));", tag, opName);
+ });
+ for (MemberShape event : events) {
+ String ev = eventShapeName(event);
+ writer.openBlock("m_on$1L = [&](const $1L&) {", "};", ev, () -> {
+ writer.write("AWS_LOGSTREAM_TRACE($1L, \"$2L received.\");", tag, ev);
+ });
+ }
+ writer.openBlock("m_onError = [&](const AWSError<$1LErrors>& error) {", "};", namespace, () -> {
+ writer.write("AWS_LOGSTREAM_TRACE($1L, \"$2L Errors received, \" << error);", tag, namespace);
+ });
+ });
+ writer.write("");
+
+ // OnEvent
+ writer.openBlock("void $1L::OnEvent() {", "}", className, () -> {
+ writer.openBlock("if (!*this) {", "}", () -> {
+ writer.write("AWSError error = EventStreamErrorsMapper::GetAwsErrorForEventStreamError(GetInternalError());");
+ writer.write("error.SetMessage(GetEventPayloadAsString());");
+ writer.write("m_onError(AWSError<$1LErrors>(error));", namespace);
+ writer.write("return;");
+ });
+ writer.write("const auto& headers = GetEventHeaders();");
+ writer.write("auto messageTypeHeaderIter = headers.find(MESSAGE_TYPE_HEADER);");
+ writer.openBlock("if (messageTypeHeaderIter == headers.end()) {", "}", () -> {
+ writer.write("AWS_LOGSTREAM_WARN($1L, \"Header: \" << MESSAGE_TYPE_HEADER << \" not found in the message.\");", tag);
+ writer.write("return;");
+ });
+ writer.openBlock("switch (Aws::Utils::Event::Message::GetMessageTypeForName(messageTypeHeaderIter->second.GetEventHeaderValueAsString())) {", "}", () -> {
+ writer.write("case Aws::Utils::Event::Message::MessageType::EVENT:");
+ writer.write("HandleEventInMessage();");
+ writer.write("break;");
+ writer.write("case Aws::Utils::Event::Message::MessageType::REQUEST_LEVEL_ERROR:");
+ writer.openBlock("case Aws::Utils::Event::Message::MessageType::REQUEST_LEVEL_EXCEPTION: {", "}", () -> {
+ writer.write("HandleErrorInMessage();");
+ writer.write("break;");
+ });
+ writer.write("default:");
+ writer.write("AWS_LOGSTREAM_WARN($1L, \"Unexpected message type: \" << messageTypeHeaderIter->second.GetEventHeaderValueAsString());", tag);
+ writer.write("break;");
+ });
+ });
+ writer.write("");
+
+ // HandleEventInMessage
+ writer.openBlock("void $1L::HandleEventInMessage() {", "}", className, () -> {
+ writer.write("const auto& headers = GetEventHeaders();");
+ writer.write("auto eventTypeHeaderIter = headers.find(EVENT_TYPE_HEADER);");
+ writer.openBlock("if (eventTypeHeaderIter == headers.end()) {", "}", () -> {
+ writer.write("AWS_LOGSTREAM_WARN($1L, \"Header: \" << EVENT_TYPE_HEADER << \" not found in the message.\");", tag);
+ writer.write("return;");
+ });
+ writer.openBlock("switch ($1LEventMapper::Get$1LEventTypeForName(eventTypeHeaderIter->second.GetEventHeaderValueAsString())) {", "}", opName, () -> {
+ writer.openBlock("case $1LEventType::INITIAL_RESPONSE: {", "}", opName, () -> {
+ writer.write("$1LInitialResponse event(GetEventHeadersAsHttpHeaders());", opName);
+ writer.write("m_onInitialResponse(event, Utils::Event::InitialResponseType::ON_EVENT);");
+ writer.write("break;");
+ });
+ for (MemberShape event : events) {
+ writer.openBlock("case $1LEventType::$2L: {", "}", opName, enumConstant(event), () -> {
+ SerdeStub.renderEventPayloadDecodeStub(writer, eventShapeName(event), "m_on" + eventShapeName(event));
+ writer.write("break;");
+ });
+ }
+ writer.write("default:");
+ writer.write("AWS_LOGSTREAM_WARN($1L, \"Unexpected event type: \" << eventTypeHeaderIter->second.GetEventHeaderValueAsString());", tag);
+ writer.write("break;");
+ });
+ });
+ writer.write("");
+
+ // HandleErrorInMessage
+ writer.openBlock("void $1L::HandleErrorInMessage() {", "}", className, () -> {
+ writer.write("const auto& headers = GetEventHeaders();");
+ writer.write("Aws::String errorCode;");
+ writer.write("Aws::String errorMessage;");
+ writer.write("auto errorHeaderIter = headers.find(ERROR_CODE_HEADER);");
+ writer.openBlock("if (errorHeaderIter == headers.end()) {", "}", () -> {
+ writer.write("errorHeaderIter = headers.find(EXCEPTION_TYPE_HEADER);");
+ writer.openBlock("if (errorHeaderIter == headers.end()) {", "}", () -> {
+ writer.write("AWS_LOGSTREAM_WARN($1L, \"Error type was not found in the event message.\");", tag);
+ writer.write("return;");
+ });
+ });
+ writer.write("errorCode = errorHeaderIter->second.GetEventHeaderValueAsString();");
+ writer.write("errorHeaderIter = headers.find(ERROR_MESSAGE_HEADER);");
+ writer.openBlock("if (errorHeaderIter == headers.end()) {", "}", () -> {
+ writer.write("// TODO: read error message from payload once protocol-specific serde lands");
+ SerdeStub.renderErrorPayloadParseStub(writer);
+ });
+ writer.openBlock("else {", "}", () -> {
+ writer.write("errorMessage = errorHeaderIter->second.GetEventHeaderValueAsString();");
+ });
+ writer.write("MarshallError(errorCode, errorMessage);");
+ });
+ writer.write("");
+
+ // MarshallError (verbatim from C2J)
+ writer.openBlock("void $1L::MarshallError(const Aws::String& errorCode, const Aws::String& errorMessage) {", "}", className, () -> {
+ writer.write("$1LErrorMarshaller errorMarshaller;", namespace);
+ writer.write("AWSError error;");
+ writer.openBlock("if (errorCode.empty()) {", "}", () -> {
+ writer.write("error = AWSError(CoreErrors::UNKNOWN, \"\", errorMessage, false);");
+ });
+ writer.openBlock("else {", "}", () -> {
+ writer.write("error = errorMarshaller.FindErrorByName(errorCode.c_str());");
+ writer.openBlock("if (error.GetErrorType() != CoreErrors::UNKNOWN) {", "}", () -> {
+ writer.write("AWS_LOGSTREAM_WARN($1L, \"Encountered AWSError '\" << errorCode.c_str() << \"': \" << errorMessage.c_str());", tag);
+ writer.write("error.SetExceptionName(errorCode);");
+ writer.write("error.SetMessage(errorMessage);");
+ });
+ writer.openBlock("else {", "}", () -> {
+ writer.write("AWS_LOGSTREAM_WARN($1L, \"Encountered Unknown AWSError '\" << errorCode.c_str() << \"': \" << errorMessage.c_str());", tag);
+ writer.write("error = AWSError(CoreErrors::UNKNOWN, errorCode, \"Unable to parse ExceptionName: \" + errorCode + \" Message: \" + errorMessage, false);");
+ });
+ });
+ writer.write("m_onError(AWSError<$1LErrors>(error));", namespace);
+ });
+ writer.write("");
+
+ // EventMapper
+ writer.writeNamespaceOpen(opName + "EventMapper");
+ writer.write("static const int INITIAL_RESPONSE_HASH = Aws::Utils::HashingUtils::HashString(\"initial-response\");");
+ for (MemberShape event : events) {
+ writer.write("static const int $1L_HASH = Aws::Utils::HashingUtils::HashString(\"$2L\");",
+ enumConstant(event), wireKey(event));
+ }
+ writer.write("");
+ writer.openBlock("$1LEventType Get$1LEventTypeForName(const Aws::String& name) {", "}", opName, () -> {
+ writer.write("int hashCode = Aws::Utils::HashingUtils::HashString(name.c_str());");
+ writer.openBlock("if (hashCode == INITIAL_RESPONSE_HASH) {", "}", () -> {
+ writer.write("return $1LEventType::INITIAL_RESPONSE;", opName);
+ });
+ for (MemberShape event : events) {
+ writer.openBlock("else if (hashCode == $1L_HASH) {", "}", enumConstant(event), () -> {
+ writer.write("return $1LEventType::$2L;", opName, enumConstant(event));
+ });
+ }
+ writer.write("return $1LEventType::UNKNOWN;", opName);
+ });
+ writer.write("");
+ writer.openBlock("Aws::String GetNameFor$1LEventType($1LEventType value) {", "}", opName, () -> {
+ writer.openBlock("switch (value) {", "}", () -> {
+ writer.write("case $1LEventType::INITIAL_RESPONSE:", opName);
+ writer.write("return \"initial-response\";");
+ for (MemberShape event : events) {
+ writer.write("case $1LEventType::$2L:", opName, enumConstant(event));
+ writer.write("return \"$1L\";", wireKey(event));
+ }
+ writer.write("default:");
+ writer.write("return \"Unknown\";");
+ });
+ });
+ writer.writeNamespaceClose(opName + "EventMapper");
+
+ writer.writeNamespaceClose("Model");
+ writer.writeNamespaceClose(namespace);
+ writer.writeNamespaceClose("Aws");
+ });
+ }
+
+ // ---- Placeholders filled in by Tasks 4 and 5 ----------------------------
+
+ private void renderInitialResponse(CppWriterDelegator writerDelegator, String opName) {
+ String className = opName + "InitialResponse";
+
+ String headerFile = "include/aws/" + smithyServiceName + "/model/" + className + ".h";
+ writerDelegator.useFileWriter(headerFile, writer -> {
+ writer.write("#pragma once");
+ writer.write("#include ");
+ writer.write("#include ", smithyServiceName, namespace);
+ writer.write("");
+ writer.writeNamespaceOpen("Aws");
+ renderSerdeForwardDeclarations(writer);
+ writer.writeNamespaceOpen(namespace);
+ writer.writeNamespaceOpen("Model");
+ writer.write("");
+ writer.openBlock("class $L {", "};", className, () -> {
+ writer.write("public:");
+ // The header-collection ctor sits before the serialize method (mainline ordering).
+ SerdeStub.renderHeaderDeclarations(writer, protocol, exportMacro, className,
+ () -> writer.write("$1L $2L(const Http::HeaderValueCollection& responseHeaders);", exportMacro, className));
+ });
+ writer.write("");
+ writer.writeNamespaceClose("Model");
+ writer.writeNamespaceClose(namespace);
+ writer.writeNamespaceClose("Aws");
+ });
+
+ String sourceFile = "source/model/" + className + ".cpp";
+ writerDelegator.useFileWriter(sourceFile, writer -> {
+ if (protocol.isJsonLike()) {
+ writer.write("#include ");
+ } else {
+ writer.write("#include ");
+ }
+ writer.write("#include ");
+ writer.write("#include ", smithyServiceName, className);
+ writer.write("");
+ writer.write("using namespace Aws::$1L::Model;", namespace);
+ if (protocol.isJsonLike()) {
+ writer.write("using namespace Aws::Utils::Json;");
+ } else {
+ writer.write("using namespace Aws::Utils::Xml;");
+ }
+ writer.write("using namespace Aws::Utils;");
+ writer.write("");
+ SerdeStub.renderSerdeSourceStub(writer, protocol, className);
+ writer.write("");
+ writer.openBlock("$1L::$1L(const Http::HeaderValueCollection& responseHeaders) {", "}", className, () -> {
+ writer.write("AWS_UNREFERENCED_PARAM(responseHeaders);");
+ });
+ });
+ }
+
+ /** Forward-declares the protocol serde types under Aws:: for header use. */
+ private void renderSerdeForwardDeclarations(CppWriter writer) {
+ if (protocol.isJsonLike()) {
+ writer.writeNamespaceOpen("Utils");
+ writer.writeNamespaceOpen("Json");
+ writer.write("class JsonValue;");
+ writer.write("class JsonView;");
+ writer.writeNamespaceClose("Json");
+ writer.writeNamespaceClose("Utils");
+ } else {
+ writer.writeNamespaceOpen("Utils");
+ writer.writeNamespaceOpen("Xml");
+ writer.write("class XmlNode;");
+ writer.writeNamespaceClose("Xml");
+ writer.writeNamespaceClose("Utils");
+ }
+ }
+
+ private void renderEventStreamUnion(CppWriterDelegator writerDelegator, String opName,
+ UnionShape union, List events,
+ List exceptions) {
+ String className = union.getId().getName();
+ String errorType = namespace + "Error";
+
+ String headerFile = "include/aws/" + smithyServiceName + "/model/" + className + ".h";
+ writerDelegator.useFileWriter(headerFile, writer -> {
+ writer.write("#pragma once");
+ writer.write("#include ", smithyServiceName, namespace);
+ writer.write("#include ", smithyServiceName, namespace);
+ for (MemberShape event : events) {
+ writer.write("#include ", smithyServiceName, eventShapeName(event));
+ }
+ writer.write("");
+ writer.write("#include ");
+ writer.write("");
+ writer.writeNamespaceOpen("Aws");
+ renderSerdeForwardDeclarations(writer);
+ writer.writeNamespaceOpen(namespace);
+ writer.writeNamespaceOpen("Model");
+ writer.write("");
+ MemberRenderer.renderClassDocComment(writer, union, smithyServiceName, service.getVersion());
+ writer.openBlock("class $L {", "};", className, () -> {
+ writer.write("public:");
+ SerdeStub.renderHeaderDeclarations(writer, protocol, exportMacro, className);
+ writer.write("");
+ // Event member accessors, typed as their concrete shape.
+ for (MemberShape event : events) {
+ String cppType = CppTypeMapper.getCppType(model.expectShape(event.getTarget()), model);
+ renderShapeAccessor(writer, className, cppType, event.getMemberName(), event);
+ }
+ // Exception member accessors, typed as Error.
+ for (MemberShape exc : exceptions) {
+ renderShapeAccessor(writer, className, errorType, exc.getMemberName(), exc);
+ }
+ writer.dedent();
+ writer.write("private:");
+ writer.indent();
+ // Data members
+ for (MemberShape event : events) {
+ String cppType = CppTypeMapper.getCppType(model.expectShape(event.getTarget()), model);
+ writer.write("$1L $2L;", cppType, "m_" + decapitalize(event.getMemberName()));
+ }
+ for (MemberShape exc : exceptions) {
+ writer.write("$1L $2L;", errorType, "m_" + decapitalize(exc.getMemberName()));
+ }
+ // HasBeenSet flags
+ for (MemberShape event : events) {
+ writer.write("bool $1LHasBeenSet = false;", "m_" + decapitalize(event.getMemberName()));
+ }
+ for (MemberShape exc : exceptions) {
+ writer.write("bool $1LHasBeenSet = false;", "m_" + decapitalize(exc.getMemberName()));
+ }
+ });
+ writer.write("");
+ writer.writeNamespaceClose("Model");
+ writer.writeNamespaceClose(namespace);
+ writer.writeNamespaceClose("Aws");
+ });
+
+ // C2J generates the event stream union as a header-only type: the serde methods are
+ // declared but never defined or referenced (the handler dispatches on the concrete
+ // event shape, not the union). No .cpp is emitted, to match mainline parity.
+ }
+
+ /**
+ * Renders a Get/HasBeenSet/Set/With accessor block for a union member, typed by the
+ * given C++ type string. Used for both event members (concrete shape type) and
+ * exception members (the service error wrapper). Mirrors the templated setter style
+ * used by MemberRenderer.
+ */
+ private void renderShapeAccessor(CppWriter writer, String className, String cppType, String memberName,
+ MemberShape member) {
+ String getter = capitalize(memberName);
+ String field = "m_" + decapitalize(memberName);
+ String templateParam = getter + "T";
+ writer.write("///@{");
+ if (member.getTrait(DocumentationTrait.class).isPresent()) {
+ MemberRenderer.writeDocComment(writer,
+ MemberRenderer.collapseWhitespace(member.getTrait(DocumentationTrait.class).get().getValue()));
+ } else {
+ writer.write("");
+ }
+ writer.write("inline const $1L& Get$2L() const { return $3L; }", cppType, getter, field);
+ writer.write("inline bool $1LHasBeenSet() const { return $2LHasBeenSet; }", getter, field);
+ writer.write("template ", templateParam, cppType);
+ writer.openBlock("void Set$1L($2L&& value) {", "}", getter, templateParam, () -> {
+ writer.write("$1LHasBeenSet = true;", field);
+ writer.write("$1L = std::forward<$2L>(value);", field, templateParam);
+ });
+ writer.write("template ", templateParam, cppType);
+ writer.openBlock("$1L& With$2L($3L&& value) {", "}", className, getter, templateParam, () -> {
+ writer.write("Set$1L(std::forward<$2L>(value));", getter, templateParam);
+ writer.write("return *this;");
+ });
+ writer.write("///@}");
+ }
+
+ private static String capitalize(String s) {
+ return s.substring(0, 1).toUpperCase() + s.substring(1);
+ }
+
+ private static String decapitalize(String s) {
+ return s.substring(0, 1).toLowerCase() + s.substring(1);
+ }
+
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/RequestRenderer.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/RequestRenderer.java
new file mode 100644
index 00000000000..44e02264a0c
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/RequestRenderer.java
@@ -0,0 +1,295 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model.renderers;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriter;
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriterDelegator;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.CppTypeMapper;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.MemberRenderer;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver.Protocol;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ShapeClassifier;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ShapeClassifier.RequestInfo;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ShapeRenderer;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.MemberShape;
+import software.amazon.smithy.model.shapes.OperationShape;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.shapes.StructureShape;
+import software.amazon.smithy.rulesengine.traits.ContextParamTrait;
+import software.amazon.smithy.rulesengine.traits.StaticContextParamsTrait;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Renders C++ headers and sources for request (operation input) shapes.
+ */
+public final class RequestRenderer implements ShapeRenderer {
+
+ private final List requests;
+ private final Model model;
+ private final ServiceShape service;
+ private final Protocol protocol;
+ private final String namespace;
+ private final String exportMacro;
+ private final String smithyServiceName;
+
+ public RequestRenderer(List requests, Model model, ServiceShape service,
+ Protocol protocol, String namespace, String exportMacro,
+ String smithyServiceName) {
+ this.requests = requests;
+ this.model = model;
+ this.service = service;
+ this.protocol = protocol;
+ this.namespace = namespace;
+ this.exportMacro = exportMacro;
+ this.smithyServiceName = smithyServiceName;
+ }
+
+ @Override
+ public void render(CppWriterDelegator writerDelegator) {
+ for (RequestInfo reqInfo : requests) {
+ renderHeader(writerDelegator, reqInfo.shape(), reqInfo.operation());
+ renderSource(writerDelegator, reqInfo.shape(), reqInfo.operation());
+ }
+ }
+
+ private void renderHeader(CppWriterDelegator writerDelegator,
+ StructureShape shape, OperationShape operation) {
+ String className = operation.getId().getName() + "Request";
+ String fileName = "include/aws/" + smithyServiceName + "/model/" + className + ".h";
+ writerDelegator.useFileWriter(fileName, writer -> {
+ writer.write("#pragma once");
+
+ Set includes = new TreeSet<>();
+ includes.add("");
+ includes.add("");
+ List memberIncludes = CppTypeMapper.getIncludesForShape(shape, model, smithyServiceName);
+ includes.addAll(memberIncludes);
+ for (String include : includes) {
+ writer.write("#include $L", include);
+ }
+
+ boolean allPrimitive = shape.getAllMembers().values().stream()
+ .map(m -> model.expectShape(m.getTarget()))
+ .allMatch(CppTypeMapper::isPrimitive);
+ if (!allPrimitive) {
+ writer.write("");
+ writer.write("#include ");
+ }
+
+ boolean streamingResponse = ShapeClassifier.isEventStreamResponseOperation(operation, model);
+ boolean streamingRequest = ShapeClassifier.isEventStreamRequestOperation(operation, model);
+ if (streamingResponse) {
+ writer.write("#include ");
+ writer.write("#include ", smithyServiceName, operation.getId().getName());
+ }
+ writer.write("");
+
+ writer.writeNamespaceOpen("Aws");
+ writer.writeNamespaceOpen(namespace);
+ writer.writeNamespaceOpen("Model");
+ writer.write("");
+
+ MemberRenderer.renderClassDocComment(writer, shape, smithyServiceName, service.getVersion());
+
+ String baseClass = namespace + "Request";
+ writer.openBlock("class $L : public $L {", "};", className, baseClass, () -> {
+ writer.write("public:");
+ writer.write("$L $L() = default;", exportMacro, className);
+ writer.write("");
+
+ writer.write("// Service request name is the Operation name which will send this request out,");
+ writer.write("// each operation should has unique request name, so that we can get operation's name from this request.");
+ writer.write("// Note: this is not true for response, multiple operations may have the same response name,");
+ writer.write("// so we can not get operation's name from response.");
+ writer.write("inline virtual const char* GetServiceRequestName() const override { return \"$L\"; }",
+ operation.getId().getName());
+ writer.write("");
+ if (streamingRequest) {
+ writer.write("inline virtual bool IsEventStreamRequest() const override { return true; }");
+ }
+ if (streamingResponse) {
+ writer.write("inline virtual bool HasEventStreamResponse() const override { return true; }");
+ }
+ writer.write("$L Aws::String SerializePayload() const override;", exportMacro);
+ writer.write("");
+ writer.write("$L Aws::Http::HeaderValueCollection GetRequestSpecificHeaders() const override;", exportMacro);
+
+ if (streamingResponse) {
+ String handlerType = operation.getId().getName() + "Handler";
+ writer.write("");
+ writer.write("/**");
+ writer.write(" * Underlying Event Stream Decoder.");
+ writer.write(" */");
+ writer.write("inline Aws::Utils::Event::EventStreamDecoder& GetEventStreamDecoder() { return m_decoder; }");
+ writer.write("");
+ writer.write("/**");
+ writer.write(" * Underlying Event Stream Handler which is used to define callback functions.");
+ writer.write(" */");
+ writer.write("inline $1L& GetEventStreamHandler() { return m_handler; }", handlerType);
+ writer.write("");
+ writer.write("/**");
+ writer.write(" * Underlying Event Stream Handler which is used to define callback functions.");
+ writer.write(" */");
+ writer.openBlock("inline void SetEventStreamHandler(const $1L& value) {", "}", handlerType, () -> {
+ writer.write("m_handler = value;");
+ writer.write("m_decoder.ResetEventStreamHandler(&m_handler);");
+ });
+ writer.write("");
+ writer.write("/**");
+ writer.write(" * Underlying Event Stream Handler which is used to define callback functions.");
+ writer.write(" */");
+ writer.openBlock("inline $1LRequest& WithEventStreamHandler(const $2L& value) {", "}",
+ operation.getId().getName(), handlerType, () -> {
+ writer.write("SetEventStreamHandler(value);");
+ writer.write("return *this;");
+ });
+ }
+
+ if (hasEndpointContextParams(operation, shape)) {
+ writer.write("");
+ writer.write("/**");
+ writer.write(" * Helper function to collect parameters (configurable and static hardcoded) required for endpoint computation.");
+ writer.write(" */");
+ writer.write("$L EndpointParameters GetEndpointContextParams() const override;", exportMacro);
+ }
+
+ writer.write("");
+ MemberRenderer.renderPublicSection(writer, shape, model, exportMacro, className);
+
+ if (!shape.getAllMembers().isEmpty()) {
+ writer.dedent();
+ writer.write("private:");
+ writer.indent();
+ if (streamingResponse) {
+ // Mainline places the handler/decoder after the data members and
+ // before the HasBeenSet flags.
+ MemberRenderer.renderPrivateDataMembers(writer, shape, model);
+ String handlerType = operation.getId().getName() + "Handler";
+ writer.write("$1L m_handler;", handlerType);
+ writer.write("Aws::Utils::Event::EventStreamDecoder m_decoder{Utils::Event::EventStreamDecoder(&m_handler)};");
+ writer.write("");
+ MemberRenderer.renderPrivateHasBeenSetFlags(writer, shape, model);
+ } else {
+ MemberRenderer.renderPrivateSection(writer, shape, model);
+ }
+ }
+ });
+ writer.write("");
+
+ writer.writeNamespaceClose("Model");
+ writer.writeNamespaceClose(namespace);
+ writer.writeNamespaceClose("Aws");
+ });
+ }
+
+ private void renderSource(CppWriterDelegator writerDelegator,
+ StructureShape shape, OperationShape operation) {
+ String className = operation.getId().getName() + "Request";
+ String fileName = "source/model/" + className + ".cpp";
+ writerDelegator.useFileWriter(fileName, writer -> {
+
+ if (protocol.isJsonLike()) {
+ writer.write("#include ");
+ } else {
+ writer.write("#include ");
+ }
+ writer.write("#include ", smithyServiceName, className);
+ writer.write("");
+ writer.write("#include ");
+ writer.write("");
+
+ writer.write("using namespace Aws::$L::Model;", namespace);
+ if (protocol.isJsonLike()) {
+ writer.write("using namespace Aws::Utils::Json;");
+ writer.write("using namespace Aws::Utils;");
+ } else {
+ writer.write("using namespace Aws::Utils::Xml;");
+ writer.write("using namespace Aws::Utils;");
+ }
+ writer.write("");
+
+ writer.write("Aws::String $L::SerializePayload() const { return \"{}\"; }", className);
+ writer.write("");
+ writer.write("Aws::Http::HeaderValueCollection $L::GetRequestSpecificHeaders() const { return {}; }", className);
+
+ if (hasEndpointContextParams(operation, shape)) {
+ writer.write("");
+ renderEndpointContextParams(writer, className, operation, shape);
+ }
+ writer.write("");
+ });
+ }
+
+ private boolean hasEndpointContextParams(OperationShape operation, StructureShape shape) {
+ if (operation.hasTrait(StaticContextParamsTrait.class)) {
+ return true;
+ }
+ for (MemberShape member : shape.getAllMembers().values()) {
+ if (member.hasTrait(ContextParamTrait.class)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void renderEndpointContextParams(CppWriter writer, String className,
+ OperationShape operation, StructureShape shape) {
+ writer.openBlock("$L::EndpointParameters $L::GetEndpointContextParams() const {", "}",
+ className, className, () -> {
+ writer.write("EndpointParameters parameters;");
+
+ if (operation.hasTrait(StaticContextParamsTrait.class)) {
+ writer.write("// Static context parameters");
+ StaticContextParamsTrait staticParams = operation.expectTrait(StaticContextParamsTrait.class);
+ staticParams.getParameters().forEach((name, def) -> {
+ def.getValue().accept(new software.amazon.smithy.model.node.NodeVisitor.Default() {
+ @Override
+ protected Void getDefault(software.amazon.smithy.model.node.Node node) {
+ return null;
+ }
+ @Override
+ public Void stringNode(software.amazon.smithy.model.node.StringNode node) {
+ writer.write("parameters.emplace_back(Aws::String(\"$L\"), \"$L\", Aws::Endpoint::EndpointParameter::ParameterOrigin::STATIC_CONTEXT);",
+ name, node.getValue());
+ return null;
+ }
+ @Override
+ public Void booleanNode(software.amazon.smithy.model.node.BooleanNode node) {
+ writer.write("parameters.emplace_back(Aws::String(\"$L\"), $L, Aws::Endpoint::EndpointParameter::ParameterOrigin::STATIC_CONTEXT);",
+ name, node.getValue());
+ return null;
+ }
+ });
+ });
+ }
+
+ boolean hasContextParams = shape.getAllMembers().values().stream()
+ .anyMatch(m -> m.hasTrait(ContextParamTrait.class));
+ if (hasContextParams) {
+ writer.write("// Operation context parameters");
+ for (Map.Entry entry : shape.getAllMembers().entrySet()) {
+ MemberShape member = entry.getValue();
+ if (member.hasTrait(ContextParamTrait.class)) {
+ ContextParamTrait contextParam = member.expectTrait(ContextParamTrait.class);
+ String paramName = contextParam.getName();
+ String memberName = entry.getKey();
+ String capitalizedMember = memberName.substring(0, 1).toUpperCase() + memberName.substring(1);
+ writer.openBlock("if ($LHasBeenSet()) {", "}", capitalizedMember, () -> {
+ writer.write("parameters.emplace_back(Aws::String(\"$L\"), this->Get$L(), Aws::Endpoint::EndpointParameter::ParameterOrigin::OPERATION_CONTEXT);",
+ paramName, capitalizedMember);
+ });
+ }
+ }
+ }
+
+ writer.write("return parameters;");
+ });
+ }
+
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/ResultRenderer.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/ResultRenderer.java
new file mode 100644
index 00000000000..f1741bcb084
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/ResultRenderer.java
@@ -0,0 +1,206 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model.renderers;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriter;
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriterDelegator;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.CppTypeMapper;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.MemberRenderer;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver.Protocol;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ShapeClassifier.ResultInfo;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ShapeRenderer;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.OperationShape;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.shapes.StructureShape;
+import software.amazon.smithy.model.traits.DocumentationTrait;
+
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Renders C++ headers and sources for result (operation output) shapes.
+ */
+public final class ResultRenderer implements ShapeRenderer {
+
+ private final List results;
+ private final Model model;
+ private final ServiceShape service;
+ private final Protocol protocol;
+ private final String namespace;
+ private final String exportMacro;
+ private final String smithyServiceName;
+
+ public ResultRenderer(List results, Model model, ServiceShape service,
+ Protocol protocol, String namespace, String exportMacro,
+ String smithyServiceName) {
+ this.results = results;
+ this.model = model;
+ this.service = service;
+ this.protocol = protocol;
+ this.namespace = namespace;
+ this.exportMacro = exportMacro;
+ this.smithyServiceName = smithyServiceName;
+ }
+
+ @Override
+ public void render(CppWriterDelegator writerDelegator) {
+ for (ResultInfo resInfo : results) {
+ renderHeader(writerDelegator, resInfo.shape(), resInfo.operation());
+ renderSource(writerDelegator, resInfo.shape(), resInfo.operation());
+ }
+ }
+
+ private void renderHeader(CppWriterDelegator writerDelegator,
+ StructureShape shape, OperationShape operation) {
+ String className = operation.getId().getName() + "Result";
+ String fileName = "include/aws/" + smithyServiceName + "/model/" + className + ".h";
+ writerDelegator.useFileWriter(fileName, writer -> {
+ writer.write("#pragma once");
+
+ Set includes = new TreeSet<>();
+ includes.add("");
+ includes.add("");
+ includes.add("");
+ List memberIncludes = CppTypeMapper.getIncludesForShape(shape, model, smithyServiceName);
+ includes.addAll(memberIncludes);
+ for (String include : includes) {
+ writer.write("#include $L", include);
+ }
+
+ writer.write("");
+ writer.write("#include ");
+ writer.write("");
+
+ writer.writeNamespaceOpen("Aws");
+ writer.write("template ");
+ writer.write("class AmazonWebServiceResult;");
+ writer.write("");
+ renderForwardDeclarations(writer);
+
+ writer.writeNamespaceOpen(namespace);
+ writer.writeNamespaceOpen("Model");
+
+ if (shape.getTrait(DocumentationTrait.class).isPresent()) {
+ MemberRenderer.renderClassDocComment(writer, shape, smithyServiceName, service.getVersion());
+ }
+
+ writer.openBlock("class $L {", "};", className, () -> {
+ writer.write("public:");
+ writer.write("$L $L() = default;", exportMacro, className);
+ if (protocol.isJsonLike()) {
+ writer.write("$L $L(const Aws::AmazonWebServiceResult& result);", exportMacro, className);
+ writer.write("$L $L& operator=(const Aws::AmazonWebServiceResult& result);", exportMacro, className);
+ } else {
+ writer.write("$L $L(const Aws::AmazonWebServiceResult& result);", exportMacro, className);
+ writer.write("$L $L& operator=(const Aws::AmazonWebServiceResult& result);", exportMacro, className);
+ }
+ writer.write("");
+
+ MemberRenderer.renderPublicSectionForResult(writer, shape, model, exportMacro, className);
+
+ writer.write("");
+ writer.write("///@{");
+ writer.write("");
+ writer.write("inline const Aws::String& GetRequestId() const { return m_requestId; }");
+ writer.write("template ");
+ writer.openBlock("void SetRequestId(RequestIdT&& value) {", "}", () -> {
+ writer.write("m_requestIdHasBeenSet = true;");
+ writer.write("m_requestId = std::forward(value);");
+ });
+ writer.write("template ");
+ writer.openBlock("$L& WithRequestId(RequestIdT&& value) {", "}", className, () -> {
+ writer.write("SetRequestId(std::forward(value));");
+ writer.write("return *this;");
+ });
+ writer.write("///@}");
+
+ writer.write("inline Aws::Http::HttpResponseCode GetHttpResponseCode() const { return m_HttpResponseCode; }");
+ writer.write("");
+
+ writer.dedent();
+ writer.write("private:");
+ writer.indent();
+ MemberRenderer.renderPrivateDataMembers(writer, shape, model);
+ writer.write("");
+ writer.write("Aws::String m_requestId;");
+ writer.write("Aws::Http::HttpResponseCode m_HttpResponseCode;");
+ MemberRenderer.renderPrivateHasBeenSetFlags(writer, shape, model);
+ writer.write("bool m_requestIdHasBeenSet = false;");
+ });
+ writer.write("");
+
+ writer.writeNamespaceClose("Model");
+ writer.writeNamespaceClose(namespace);
+ writer.writeNamespaceClose("Aws");
+ });
+ }
+
+ private void renderSource(CppWriterDelegator writerDelegator,
+ StructureShape shape, OperationShape operation) {
+ String className = operation.getId().getName() + "Result";
+ String fileName = "source/model/" + className + ".cpp";
+ writerDelegator.useFileWriter(fileName, writer -> {
+
+ writer.write("#include ");
+ writer.write("#include ");
+ writer.write("#include ");
+ if (protocol.isJsonLike()) {
+ writer.write("#include ");
+ } else {
+ writer.write("#include ");
+ }
+ writer.write("#include ");
+ writer.write("#include ", smithyServiceName, className);
+ writer.write("");
+ writer.write("#include ");
+ writer.write("");
+ if (protocol.isJsonLike()) {
+ writer.write("using namespace Aws::$L::Model;", namespace);
+ writer.write("using namespace Aws::Utils::Json;");
+ writer.write("using namespace Aws::Utils;");
+ writer.write("using namespace Aws;");
+ } else {
+ writer.write("using namespace Aws::$L::Model;", namespace);
+ writer.write("using namespace Aws::Utils::Xml;");
+ writer.write("using namespace Aws::Utils;");
+ writer.write("using namespace Aws;");
+ }
+ writer.write("");
+
+ if (protocol.isJsonLike()) {
+ writer.openBlock("$L::$L(const Aws::AmazonWebServiceResult& result) {", "}",
+ className, className, () -> writer.write("*this = result;"));
+ writer.write("");
+ writer.openBlock("$L& $L::operator=(const Aws::AmazonWebServiceResult& result) {", "}",
+ className, className, () -> writer.write("return *this;"));
+ } else {
+ writer.openBlock("$L::$L(const Aws::AmazonWebServiceResult& result) {", "}",
+ className, className, () -> writer.write("*this = result;"));
+ writer.write("");
+ writer.openBlock("$L& $L::operator=(const Aws::AmazonWebServiceResult& result) {", "}",
+ className, className, () -> writer.write("return *this;"));
+ }
+ writer.write("");
+ });
+ }
+
+ private void renderForwardDeclarations(CppWriter writer) {
+ if (protocol.isJsonLike()) {
+ writer.writeNamespaceOpen("Utils");
+ writer.writeNamespaceOpen("Json");
+ writer.write("class JsonValue;");
+ writer.writeNamespaceClose("Json");
+ writer.writeNamespaceClose("Utils");
+ } else {
+ writer.writeNamespaceOpen("Utils");
+ writer.writeNamespaceOpen("Xml");
+ writer.write("class XmlDocument;");
+ writer.writeNamespaceClose("Xml");
+ writer.writeNamespaceClose("Utils");
+ }
+ }
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/SubObjectRenderer.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/SubObjectRenderer.java
new file mode 100644
index 00000000000..5c4d3fab022
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/renderers/SubObjectRenderer.java
@@ -0,0 +1,167 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model.renderers;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriter;
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriterDelegator;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.CppTypeMapper;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.MemberRenderer;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver.Protocol;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.SerdeStub;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ShapeRenderer;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.shapes.StructureShape;
+
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Renders C++ headers and sources for sub-object (intermediate structure) shapes.
+ */
+public final class SubObjectRenderer implements ShapeRenderer {
+
+ private final List subObjects;
+ private final Model model;
+ private final ServiceShape service;
+ private final Protocol protocol;
+ private final String namespace;
+ private final String exportMacro;
+ private final String serviceName;
+ private final String smithyServiceName;
+
+ public SubObjectRenderer(List subObjects, Model model, ServiceShape service,
+ Protocol protocol, String namespace, String exportMacro,
+ String serviceName, String smithyServiceName) {
+ this.subObjects = subObjects;
+ this.model = model;
+ this.service = service;
+ this.protocol = protocol;
+ this.namespace = namespace;
+ this.exportMacro = exportMacro;
+ this.serviceName = serviceName;
+ this.smithyServiceName = smithyServiceName;
+ }
+
+ @Override
+ public void render(CppWriterDelegator writerDelegator) {
+ for (Shape shape : subObjects) {
+ if (shape.isStructureShape()) {
+ StructureShape struct = shape.asStructureShape().get();
+ renderHeader(writerDelegator, struct);
+ renderSource(writerDelegator, struct);
+ }
+ }
+ }
+
+ private void renderHeader(CppWriterDelegator writerDelegator, StructureShape shape) {
+ String className = shape.getId().getName();
+ String fileName = "include/aws/" + smithyServiceName + "/model/" + className + ".h";
+ writerDelegator.useFileWriter(fileName, writer -> {
+ writer.write("#pragma once");
+
+ // Includes
+ Set includes = new TreeSet<>();
+ includes.add("");
+ List memberIncludes = CppTypeMapper.getIncludesForShape(shape, model, smithyServiceName);
+ includes.addAll(memberIncludes);
+ for (String include : includes) {
+ writer.write("#include $L", include);
+ }
+
+ boolean allPrimitive = shape.getAllMembers().values().stream()
+ .map(m -> model.expectShape(m.getTarget()))
+ .allMatch(CppTypeMapper::isPrimitive);
+ if (!allPrimitive) {
+ writer.write("");
+ writer.write("#include ");
+ }
+ writer.write("");
+
+ writer.writeNamespaceOpen("Aws");
+ renderForwardDeclarations(writer);
+ writer.writeNamespaceOpen(namespace);
+ writer.writeNamespaceOpen("Model");
+ writer.write("");
+
+ MemberRenderer.renderClassDocComment(writer, shape, smithyServiceName, service.getVersion());
+
+ writer.openBlock("class $L {", "};", className, () -> {
+ writer.write("public:");
+ SerdeStub.renderHeaderDeclarations(writer, protocol, exportMacro, className);
+ writer.write("");
+ MemberRenderer.renderPublicSection(writer, shape, model, exportMacro, className);
+ writer.dedent();
+ writer.write("private:");
+ writer.indent();
+ MemberRenderer.renderPrivateSection(writer, shape, model);
+ });
+ writer.write("");
+
+ writer.writeNamespaceClose("Model");
+ writer.writeNamespaceClose(namespace);
+ writer.writeNamespaceClose("Aws");
+ });
+ }
+
+ private void renderSource(CppWriterDelegator writerDelegator, StructureShape shape) {
+ String className = shape.getId().getName();
+ String fileName = "source/model/" + className + ".cpp";
+ writerDelegator.useFileWriter(fileName, writer -> {
+
+ if (protocol.isJsonLike()) {
+ writer.write("#include ");
+ } else {
+ writer.write("#include ");
+ }
+ writer.write("#include ", smithyServiceName, className);
+ writer.write("");
+ writer.write("#include ");
+ writer.write("");
+
+ if (protocol.isJsonLike()) {
+ writer.write("using namespace Aws::Utils::Json;");
+ writer.write("using namespace Aws::Utils;");
+ } else {
+ writer.write("using namespace Aws::Utils::Xml;");
+ writer.write("using namespace Aws::Utils;");
+ }
+ writer.write("");
+
+ writer.writeNamespaceOpen("Aws");
+ writer.writeNamespaceOpen(namespace);
+ writer.writeNamespaceOpen("Model");
+ writer.write("");
+
+ SerdeStub.renderSourceImplementation(writer, shape, model, protocol,
+ className, serviceName, smithyServiceName);
+ writer.write("");
+
+ writer.writeNamespaceClose("Model");
+ writer.writeNamespaceClose(namespace);
+ writer.writeNamespaceClose("Aws");
+ });
+ }
+
+ private void renderForwardDeclarations(CppWriter writer) {
+ if (protocol.isJsonLike()) {
+ writer.writeNamespaceOpen("Utils");
+ writer.writeNamespaceOpen("Json");
+ writer.write("class JsonValue;");
+ writer.write("class JsonView;");
+ writer.writeNamespaceClose("Json");
+ writer.writeNamespaceClose("Utils");
+ } else {
+ writer.writeNamespaceOpen("Utils");
+ writer.writeNamespaceOpen("Xml");
+ writer.write("class XmlNode;");
+ writer.writeNamespaceClose("Xml");
+ writer.writeNamespaceClose("Utils");
+ }
+ }
+
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/transforms/GlobalTransforms.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/transforms/GlobalTransforms.java
index 055d95a8862..122946d678e 100644
--- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/transforms/GlobalTransforms.java
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/transforms/GlobalTransforms.java
@@ -4,6 +4,7 @@
*/
package com.amazonaws.util.awsclientsmithygenerator.generators.model.transforms;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ModelTransform;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.TopDownIndex;
import software.amazon.smithy.model.shapes.ListShape;
@@ -116,4 +117,15 @@ public static Set computeReachableShapes(Model model, ServiceShape serv
}
return reachable;
}
+
+ /**
+ * Returns this class as a no-op ModelTransform.
+ *
+ * GlobalTransforms currently operates at classification time (via computeReachableShapes),
+ * not at model-transform time. This factory exists to reserve a slot in the pipeline
+ * for future pre-generation model mutations (shape renaming, injection, etc.).
+ */
+ public static ModelTransform asTransform() {
+ return (model, service) -> model;
+ }
}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/waiters/WaiterEmptyHeaderGenerator.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/waiters/WaiterEmptyHeaderGenerator.java
index af15a7d96fb..1b2cc978dc7 100644
--- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/waiters/WaiterEmptyHeaderGenerator.java
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/waiters/WaiterEmptyHeaderGenerator.java
@@ -36,11 +36,6 @@ public static void generate(PluginContext context) {
CppWriterDelegator writers = new CppWriterDelegator(context.getFileManifest());
writers.useFileWriter("include/aws/" + c2jName + "/" + serviceName + "Waiter.h", writer -> {
- writer.write("/**");
- writer.write(" * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.");
- writer.write(" * SPDX-License-Identifier: Apache-2.0.");
- writer.write(" */");
- writer.write("");
writer.write("#pragma once");
writer.write("");
writer.writeNamespaceOpen("Aws");
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/EnumRendererTest.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/EnumRendererTest.java
index 23af527cfb2..806c523a5d1 100644
--- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/EnumRendererTest.java
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/EnumRendererTest.java
@@ -116,8 +116,10 @@ void renderSource_producesGetNameForFunction() {
CppWriter writer = new CppWriter();
EnumRenderer.renderSource(writer, enumShape, "Kinesis", "AWS_KINESIS_API", "kinesis");
String output = writer.toString();
- assertTrue(output.contains("Aws::String GetNameForStatus(Status value)"),
+ assertTrue(output.contains("Aws::String GetNameForStatus(Status enumValue)"),
"Missing GetNameFor function: " + output);
+ assertTrue(output.contains("case Status::NOT_SET:"),
+ "Missing NOT_SET case: " + output);
assertTrue(output.contains("case Status::CREATING:"),
"Missing CREATING case: " + output);
assertTrue(output.contains("case Status::ACTIVE:"),
@@ -137,7 +139,7 @@ void renderSource_includesOverflowContainer() {
"Missing overflow container in GetForName: " + output);
assertTrue(output.contains("overflowContainer->StoreOverflow(hashCode, name)"),
"Missing StoreOverflow: " + output);
- assertTrue(output.contains("overflowContainer->RetrieveOverflow(static_cast(value))"),
+ assertTrue(output.contains("overflowContainer->RetrieveOverflow(static_cast(enumValue))"),
"Missing RetrieveOverflow: " + output);
}
@@ -282,4 +284,12 @@ void sanitizeEnumValue_handlesForbiddenWords() {
assertEquals("NULL_", EnumRenderer.sanitizeEnumValue("NULL"));
assertEquals("OVERFLOW_", EnumRenderer.sanitizeEnumValue("OVERFLOW"));
}
+
+ @Test
+ void sanitizeEnumValue_handlesCorrectedForbiddenWords() {
+ // Verify co_yield (was misspelled as co_yeild) is now correctly forbidden
+ assertEquals("co_yield_", EnumRenderer.sanitizeEnumValue("co_yield"));
+ // Verify module (was misspelled as moduel) is now correctly forbidden
+ assertEquals("module_", EnumRenderer.sanitizeEnumValue("module"));
+ }
}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/EventStreamRendererTest.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/EventStreamRendererTest.java
new file mode 100644
index 00000000000..78d86e3d000
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/EventStreamRendererTest.java
@@ -0,0 +1,182 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriterDelegator;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.renderers.EventStreamRenderer;
+import org.junit.jupiter.api.Test;
+import software.amazon.smithy.build.MockManifest;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.shapes.StringShape;
+import software.amazon.smithy.model.shapes.StructureShape;
+import software.amazon.smithy.model.shapes.UnionShape;
+import software.amazon.smithy.model.traits.DocumentationTrait;
+import software.amazon.smithy.model.traits.ErrorTrait;
+import software.amazon.smithy.model.traits.StreamingTrait;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class EventStreamRendererTest {
+
+ private static Model twoEventModel() {
+ StringShape str = StringShape.builder().id("com.example#String").build();
+ StructureShape eventA = StructureShape.builder()
+ .id("com.example#AlphaEvent")
+ .addMember("data", str.getId())
+ .build();
+ StructureShape eventB = StructureShape.builder()
+ .id("com.example#BetaEvent")
+ .addMember("data", str.getId())
+ .build();
+ StructureShape exc = StructureShape.builder()
+ .id("com.example#BadException")
+ .addTrait(new ErrorTrait("client"))
+ .addMember("message", str.getId())
+ .build();
+ UnionShape stream = UnionShape.builder()
+ .id("com.example#MyStreamEventStream")
+ .addTrait(new StreamingTrait())
+ .addTrait(new DocumentationTrait("Tagged union of stream events.
"))
+ .addMember("alpha", eventA.getId(),
+ b -> b.addTrait(new DocumentationTrait("Alpha event doc.
")))
+ .addMember("beta", eventB.getId())
+ .addMember("badException", exc.getId())
+ .build();
+ StructureShape input = StructureShape.builder()
+ .id("com.example#DoStreamInput")
+ .addMember("name", str.getId())
+ .build();
+ StructureShape output = StructureShape.builder()
+ .id("com.example#DoStreamOutput")
+ .addMember("stream", stream.getId())
+ .build();
+ software.amazon.smithy.model.shapes.OperationShape op =
+ software.amazon.smithy.model.shapes.OperationShape.builder()
+ .id("com.example#DoStream")
+ .input(input.getId())
+ .output(output.getId())
+ .build();
+ ServiceShape service = ServiceShape.builder()
+ .id("com.example#Example")
+ .version("2024-01-01")
+ .addOperation(op.getId())
+ .build();
+ return Model.builder().addShapes(str, stream, eventA, eventB, exc, input, output, op, service).build();
+ }
+
+ private static String render(String fileSuffix) {
+ Model model = twoEventModel();
+ ServiceShape service = model.expectShape(ShapeId.from("com.example#Example"), ServiceShape.class);
+ MockManifest manifest = new MockManifest();
+ CppWriterDelegator delegator = new CppWriterDelegator(manifest);
+ EventStreamRenderer renderer = new EventStreamRenderer(
+ ShapeClassifier.classify(model, service).eventStreamHandlers(),
+ model, service, ProtocolResolver.resolve(service, model),
+ "Example", "AWS_EXAMPLE_API", "example");
+ renderer.render(delegator);
+ delegator.flushWriters();
+ return manifest.getFileString(
+ manifest.getFiles().stream()
+ .filter(p -> p.toString().endsWith(fileSuffix))
+ .findFirst().orElseThrow())
+ .orElseThrow();
+ }
+
+ @Test
+ void handlerHeader_hasEnumAndCallbacksPerEvent() {
+ String h = render("DoStreamHandler.h");
+ assertTrue(h.contains("enum class DoStreamEventType"), "Missing EventType enum: " + h);
+ assertTrue(h.contains("INITIAL_RESPONSE"), "Missing INITIAL_RESPONSE: " + h);
+ assertTrue(h.contains("ALPHA") && h.contains("BETA"), "Missing event enum constants: " + h);
+ assertTrue(h.contains("UNKNOWN"), "Missing UNKNOWN: " + h);
+ assertTrue(h.contains("SetAlphaEventCallback") && h.contains("SetBetaEventCallback"),
+ "Missing per-event setters: " + h);
+ assertTrue(h.contains("m_onAlphaEvent") && h.contains("m_onBetaEvent"),
+ "Missing per-event members: " + h);
+ assertTrue(h.contains("void OnEvent() override"), "Missing OnEvent override: " + h);
+ // Exception must NOT get its own callback
+ assertFalse(h.contains("SetBadExceptionCallback"), "Exception should not get a callback: " + h);
+ }
+
+ @Test
+ void handlerHeader_wrapsInitialResponseSettersInDocGroup() {
+ String h = render("DoStreamHandler.h");
+ assertTrue(h.contains("Sets an initial response callback."),
+ "Missing initial-response setter doc: " + h);
+ assertTrue(h.contains("a legacy one that does not distinguish"),
+ "Missing legacy initial-response setter doc: " + h);
+ // The two initial-response setters must be inside a ///@{ ... ///@} group.
+ int groupOpen = h.indexOf("///@{");
+ int exSetter = h.indexOf("SetInitialResponseCallbackEx");
+ int groupClose = h.indexOf("///@}", exSetter);
+ assertTrue(groupOpen >= 0 && groupOpen < exSetter && groupClose > exSetter,
+ "Initial-response setters must be wrapped in a ///@{ ... ///@} group: " + h);
+ }
+
+ @Test
+ void eventStreamUnionHeader_typesEventAndExceptionMembers() {
+ String h = render("MyStreamEventStream.h");
+ // NOTE: file name derives from the UNION shape name (MyStream), not the operation.
+ assertTrue(h.contains("class MyStreamEventStream"), "Missing union class: " + h);
+ // Event member typed as its concrete shape
+ assertTrue(h.contains("const AlphaEvent& GetAlpha()") || h.contains("GetAlpha"),
+ "Missing event accessor: " + h);
+ // Exception member typed as Error
+ assertTrue(h.contains("ExampleError"), "Exception members must be typed as ExampleError: " + h);
+ }
+
+ @Test
+ void eventStreamUnionHeader_rendersClassAndMemberDocs() {
+ String h = render("MyStreamEventStream.h");
+ // Union class-level documentation + See Also link.
+ assertTrue(h.contains("Tagged union of stream events."), "Missing union class doc: " + h);
+ assertTrue(h.contains("See Also:"), "Missing See Also block on union class: " + h);
+ // Member-level doc flows to the accessor for the member that has one.
+ assertTrue(h.contains("Alpha event doc."), "Missing alpha member doc: " + h);
+ }
+
+ @Test
+ void initialResponseHeader_hasHeaderCollectionCtorAndSerdeDecls() {
+ String h = render("DoStreamInitialResponse.h");
+ assertTrue(h.contains("class DoStreamInitialResponse"), "Missing class: " + h);
+ assertTrue(h.contains("DoStreamInitialResponse(const Http::HeaderValueCollection& responseHeaders)"),
+ "Missing header-collection ctor: " + h);
+ // JSON serde declaration present (test model resolves to a JSON-like protocol)
+ assertTrue(h.contains("Jsonize") || h.contains("XmlNode") || h.contains("OutputToStream"),
+ "Missing serde declarations: " + h);
+ // The HeaderValueCollection ctor must precede Jsonize (mainline ordering).
+ int headerCtor = h.indexOf("const Http::HeaderValueCollection& responseHeaders");
+ int jsonize = h.indexOf("Jsonize");
+ assertTrue(headerCtor >= 0 && jsonize >= 0 && headerCtor < jsonize,
+ "HeaderValueCollection ctor must come before Jsonize: " + h);
+ }
+
+ @Test
+ void initialResponseSource_hasHeaderCtorAndSerdeStub() {
+ String c = render("DoStreamInitialResponse.cpp");
+ assertTrue(c.contains("DoStreamInitialResponse::DoStreamInitialResponse(const Http::HeaderValueCollection&"),
+ "Missing header ctor definition: " + c);
+ }
+
+ @Test
+ void handlerSource_eventCasesUseStubNoProtocolTokens() {
+ String c = render("DoStreamHandler.cpp");
+ assertTrue(c.contains("// TODO: protocol-specific event payload deserialization"),
+ "Missing event decode stub: " + c);
+ assertTrue(c.contains("m_onAlphaEvent(AlphaEvent{});"), "Missing alpha stub body: " + c);
+ assertTrue(c.contains("case DoStreamEventType::INITIAL_RESPONSE:"),
+ "Missing INITIAL_RESPONSE case: " + c);
+ assertTrue(c.contains("// TODO: protocol-specific error payload deserialization"),
+ "Missing error parse stub: " + c);
+ // EventMapper hash uses the wire member key, not the shape name
+ assertTrue(c.contains("HashString(\"alpha\")") && c.contains("HashString(\"beta\")"),
+ "EventMapper must key on wire member name: " + c);
+ assertFalse(c.contains("JsonValue"), "No protocol tokens in handler: " + c);
+ assertFalse(c.contains("Cbor"), "No protocol tokens in handler: " + c);
+ }
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/RequestRendererTest.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/RequestRendererTest.java
new file mode 100644
index 00000000000..d08fe24fe09
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/RequestRendererTest.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriterDelegator;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.renderers.RequestRenderer;
+import org.junit.jupiter.api.Test;
+import software.amazon.smithy.build.MockManifest;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.OperationShape;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.shapes.StringShape;
+import software.amazon.smithy.model.shapes.StructureShape;
+import software.amazon.smithy.model.shapes.UnionShape;
+import software.amazon.smithy.model.traits.StreamingTrait;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class RequestRendererTest {
+
+ private static Model streamingModel(boolean inputStreams, boolean outputStreams) {
+ StringShape str = StringShape.builder().id("com.example#String").build();
+ StructureShape eventA = StructureShape.builder()
+ .id("com.example#AlphaEvent")
+ .addMember("data", str.getId())
+ .build();
+ UnionShape stream = UnionShape.builder()
+ .id("com.example#MyStreamEventStream")
+ .addTrait(new StreamingTrait())
+ .addMember("alpha", eventA.getId())
+ .build();
+
+ StructureShape.Builder inputBuilder = StructureShape.builder().id("com.example#DoStreamInput");
+ inputBuilder.addMember("name", str.getId());
+ if (inputStreams) {
+ inputBuilder.addMember("body", stream.getId());
+ }
+ StructureShape input = inputBuilder.build();
+
+ StructureShape.Builder outputBuilder = StructureShape.builder().id("com.example#DoStreamOutput");
+ if (outputStreams) {
+ outputBuilder.addMember("stream", stream.getId());
+ } else {
+ outputBuilder.addMember("result", str.getId());
+ }
+ StructureShape output = outputBuilder.build();
+
+ OperationShape op = OperationShape.builder()
+ .id("com.example#DoStream")
+ .input(input.getId())
+ .output(output.getId())
+ .build();
+ ServiceShape service = ServiceShape.builder()
+ .id("com.example#Example")
+ .version("2024-01-01")
+ .addOperation(op.getId())
+ .build();
+ return Model.builder().addShapes(str, eventA, stream, input, output, op, service).build();
+ }
+
+ private static String renderRequestHeaderForStreamingOp(boolean inputStreams, boolean outputStreams) {
+ Model model = streamingModel(inputStreams, outputStreams);
+ ServiceShape service = model.expectShape(ShapeId.from("com.example#Example"), ServiceShape.class);
+ MockManifest manifest = new MockManifest();
+ CppWriterDelegator delegator = new CppWriterDelegator(manifest);
+ RequestRenderer renderer = new RequestRenderer(
+ ShapeClassifier.classify(model, service).requests(),
+ model, service, ProtocolResolver.resolve(service, model),
+ "Example", "AWS_EXAMPLE_API", "example");
+ renderer.render(delegator);
+ delegator.flushWriters();
+ return manifest.getFileString(
+ manifest.getFiles().stream()
+ .filter(p -> p.toString().endsWith("DoStreamRequest.h"))
+ .findFirst().orElseThrow())
+ .orElseThrow();
+ }
+
+ @Test
+ void streamingResponseRequest_hasEventStreamAugmentation() {
+ // Model: operation with streaming OUTPUT only (like SubscribeToShard / ConverseStream)
+ String h = renderRequestHeaderForStreamingOp(false, true);
+ assertTrue(h.contains("bool HasEventStreamResponse() const override { return true; }"),
+ "Missing HasEventStreamResponse: " + h);
+ assertTrue(h.contains("GetEventStreamDecoder()"), "Missing decoder accessor: " + h);
+ assertTrue(h.contains("GetEventStreamHandler()"), "Missing handler accessor: " + h);
+ assertTrue(h.contains("SetEventStreamHandler") && h.contains("WithEventStreamHandler"),
+ "Missing handler setters: " + h);
+ assertTrue(h.contains("m_decoder.ResetEventStreamHandler(&m_handler)"),
+ "Missing decoder reset in setter: " + h);
+ assertTrue(h.contains("EventStreamDecoder m_decoder"), "Missing decoder member: " + h);
+ assertTrue(h.contains("#include "),
+ "Missing decoder include: " + h);
+ assertFalse(h.contains("IsEventStreamRequest"),
+ "Response-only op must not declare IsEventStreamRequest: " + h);
+ // Mainline ordering: handler/decoder sit AFTER the data members and BEFORE the
+ // HasBeenSet flags (not at the top of the private block). Target the member
+ // DECLARATION ("DoStreamHandler m_handler;"), not the public getter body.
+ int dataMember = h.indexOf("Aws::String m_name;");
+ int handlerDecl = h.indexOf("DoStreamHandler m_handler;");
+ int firstFlag = h.indexOf("HasBeenSet = false;");
+ assertTrue(dataMember >= 0 && handlerDecl > dataMember,
+ "m_handler declaration must come after data members: " + h);
+ assertTrue(firstFlag >= 0 && handlerDecl < firstFlag,
+ "m_handler declaration must come before HasBeenSet flags: " + h);
+ }
+
+ @Test
+ void bidirectionalRequest_alsoHasIsEventStreamRequest() {
+ String h = renderRequestHeaderForStreamingOp(true, true);
+ assertTrue(h.contains("bool IsEventStreamRequest() const override { return true; }"),
+ "Missing IsEventStreamRequest for bidirectional op: " + h);
+ assertTrue(h.contains("bool HasEventStreamResponse() const override { return true; }"),
+ "Bidirectional op must also declare HasEventStreamResponse: " + h);
+ }
+
+ @Test
+ void nonStreamingRequest_hasNoEventStreamAugmentation() {
+ String h = renderRequestHeaderForStreamingOp(false, false);
+ assertFalse(h.contains("HasEventStreamResponse"), "Non-streaming request must be clean: " + h);
+ assertFalse(h.contains("GetEventStreamDecoder"), "Non-streaming request must be clean: " + h);
+ assertFalse(h.contains("IsEventStreamRequest"), "Non-streaming request must be clean: " + h);
+ }
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/SerdeStubTest.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/SerdeStubTest.java
new file mode 100644
index 00000000000..2335c427e3a
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/SerdeStubTest.java
@@ -0,0 +1,49 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriter;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver.Protocol;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class SerdeStubTest {
+
+ @Test
+ void renderEventPayloadDecodeStub_emitsTodoAndNoProtocolTokens() {
+ CppWriter writer = new CppWriter();
+ SerdeStub.renderEventPayloadDecodeStub(writer, "SubscribeToShardEvent", "m_onSubscribeToShardEvent");
+ String out = writer.toString();
+ assertTrue(out.contains("// TODO: protocol-specific event payload deserialization"),
+ "Missing TODO marker: " + out);
+ assertTrue(out.contains("m_onSubscribeToShardEvent(SubscribeToShardEvent{});"),
+ "Missing callback invocation: " + out);
+ assertFalse(out.contains("JsonValue"), "Should not emit JSON tokens: " + out);
+ assertFalse(out.contains("Cbor"), "Should not emit CBOR tokens: " + out);
+ }
+
+ @Test
+ void renderErrorPayloadParseStub_emitsTodoOnly() {
+ CppWriter writer = new CppWriter();
+ SerdeStub.renderErrorPayloadParseStub(writer);
+ String out = writer.toString();
+ assertTrue(out.contains("// TODO: protocol-specific error payload deserialization"),
+ "Missing TODO marker: " + out);
+ assertFalse(out.contains("JsonView"), "Should not emit JSON tokens: " + out);
+ }
+
+ @Test
+ void renderSerdeSourceStub_json_emitsEmptyBodies() {
+ CppWriter writer = new CppWriter();
+ SerdeStub.renderSerdeSourceStub(writer, Protocol.JSON, "SubscribeToShardEventStream");
+ String out = writer.toString();
+ assertTrue(out.contains("SubscribeToShardEventStream::SubscribeToShardEventStream(JsonView jsonValue)"),
+ "Missing JSON ctor stub: " + out);
+ assertTrue(out.contains("JsonValue SubscribeToShardEventStream::Jsonize() const"),
+ "Missing Jsonize stub: " + out);
+ }
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ShapeClassifierTest.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ShapeClassifierTest.java
index 20f43a4dc95..6fe308881af 100644
--- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ShapeClassifierTest.java
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ShapeClassifierTest.java
@@ -53,7 +53,7 @@ void classifiesRequestShape() {
ServiceShape service = model.expectShape(ShapeId.from("com.example#TestService"), ServiceShape.class);
var classified = ShapeClassifier.classify(model, service);
assertTrue(classified.requests().stream()
- .anyMatch(s -> s.getId().getName().equals("GetItemRequest")));
+ .anyMatch(r -> r.shape().getId().getName().equals("GetItemRequest")));
}
@Test
@@ -62,7 +62,7 @@ void classifiesResultShape() {
ServiceShape service = model.expectShape(ShapeId.from("com.example#TestService"), ServiceShape.class);
var classified = ShapeClassifier.classify(model, service);
assertTrue(classified.results().stream()
- .anyMatch(s -> s.getId().getName().equals("GetItemResponse")));
+ .anyMatch(r -> r.shape().getId().getName().equals("GetItemResponse")));
}
@Test
@@ -239,11 +239,68 @@ void classifiesEventStreamHandler() {
// Result should NOT include the event-stream-bearing output
assertTrue(classified.results().stream()
- .noneMatch(s -> s.getId().getName().equals("SubscribeResponse")));
+ .noneMatch(r -> r.shape().getId().getName().equals("SubscribeResponse")));
// Request should still be classified
assertTrue(classified.requests().stream()
- .anyMatch(s -> s.getId().getName().equals("SubscribeRequest")));
+ .anyMatch(r -> r.shape().getId().getName().equals("SubscribeRequest")));
+ }
+
+ private static Model modelWithStreaming(boolean inputStreams, boolean outputStreams) {
+ StringShape str = StringShape.builder().id("com.example#String").build();
+ UnionShape eventStream = UnionShape.builder()
+ .id("com.example#EventStream")
+ .addTrait(new StreamingTrait())
+ .addMember("event", str.getId())
+ .build();
+
+ StructureShape.Builder inputBuilder = StructureShape.builder().id("com.example#OpInput");
+ if (inputStreams) {
+ inputBuilder.addMember("body", eventStream.getId());
+ } else {
+ inputBuilder.addMember("name", str.getId());
+ }
+ StructureShape input = inputBuilder.build();
+
+ StructureShape.Builder outputBuilder = StructureShape.builder().id("com.example#OpOutput");
+ if (outputStreams) {
+ outputBuilder.addMember("stream", eventStream.getId());
+ } else {
+ outputBuilder.addMember("result", str.getId());
+ }
+ StructureShape output = outputBuilder.build();
+
+ OperationShape op = OperationShape.builder()
+ .id("com.example#Op")
+ .input(input.getId())
+ .output(output.getId())
+ .build();
+
+ return Model.builder().addShapes(str, eventStream, input, output, op).build();
+ }
+
+ @Test
+ void isEventStreamResponseOperation_trueWhenOutputStreams() {
+ Model model = modelWithStreaming(false, true);
+ OperationShape op = model.expectShape(ShapeId.from("com.example#Op"), OperationShape.class);
+ assertTrue(ShapeClassifier.isEventStreamResponseOperation(op, model));
+ assertFalse(ShapeClassifier.isEventStreamRequestOperation(op, model));
+ }
+
+ @Test
+ void isEventStreamRequestOperation_trueWhenInputStreams() {
+ Model model = modelWithStreaming(true, true);
+ OperationShape op = model.expectShape(ShapeId.from("com.example#Op"), OperationShape.class);
+ assertTrue(ShapeClassifier.isEventStreamRequestOperation(op, model));
+ assertTrue(ShapeClassifier.isEventStreamResponseOperation(op, model));
+ }
+
+ @Test
+ void isEventStreamResponseOperation_falseWhenNeitherStreams() {
+ Model model = modelWithStreaming(false, false);
+ OperationShape op = model.expectShape(ShapeId.from("com.example#Op"), OperationShape.class);
+ assertFalse(ShapeClassifier.isEventStreamResponseOperation(op, model));
+ assertFalse(ShapeClassifier.isEventStreamRequestOperation(op, model));
}
@Test
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/TransformPipelineTest.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/TransformPipelineTest.java
new file mode 100644
index 00000000000..c178d0abb85
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/TransformPipelineTest.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model;
+
+import org.junit.jupiter.api.Test;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.shapes.ShapeId;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+class TransformPipelineTest {
+
+ @Test
+ void emptyPipelineReturnsModelUnchanged() {
+ Model model = Model.builder()
+ .addShape(ServiceShape.builder().id("com.example#TestService").version("2024-01-01").build())
+ .build();
+ ServiceShape service = model.expectShape(ShapeId.from("com.example#TestService"), ServiceShape.class);
+
+ TransformPipeline pipeline = new TransformPipeline(List.of());
+ Model result = pipeline.apply(model, service);
+
+ assertSame(model, result);
+ }
+
+ @Test
+ void transformsExecuteInOrder() {
+ Model model = Model.builder()
+ .addShape(ServiceShape.builder().id("com.example#TestService").version("2024-01-01").build())
+ .build();
+ ServiceShape service = model.expectShape(ShapeId.from("com.example#TestService"), ServiceShape.class);
+
+ List executionOrder = new ArrayList<>();
+ ModelTransform first = (m, s) -> { executionOrder.add("first"); return m; };
+ ModelTransform second = (m, s) -> { executionOrder.add("second"); return m; };
+
+ TransformPipeline pipeline = new TransformPipeline(List.of(first, second));
+ pipeline.apply(model, service);
+
+ assertEquals(List.of("first", "second"), executionOrder);
+ }
+
+ @Test
+ void transformReceivesOutputOfPrevious() {
+ Model original = Model.builder()
+ .addShape(ServiceShape.builder().id("com.example#TestService").version("2024-01-01").build())
+ .build();
+ ServiceShape service = original.expectShape(ShapeId.from("com.example#TestService"), ServiceShape.class);
+
+ // First transform adds a new shape
+ Model withExtra = original.toBuilder()
+ .addShape(ServiceShape.builder().id("com.example#Extra").version("2024-01-01").build())
+ .build();
+
+ ModelTransform addShape = (m, s) -> withExtra;
+ ModelTransform checkShape = (m, s) -> {
+ // This transform should see the shape added by the first
+ m.expectShape(ShapeId.from("com.example#Extra"));
+ return m;
+ };
+
+ TransformPipeline pipeline = new TransformPipeline(List.of(addShape, checkShape));
+ Model result = pipeline.apply(original, service);
+
+ // Final result is the model from the last transform
+ assertSame(withExtra, result);
+ }
+}