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..2a70d3e52c9 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,27 @@
*/
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 com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol.ProtocolTraits;
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;
@@ -34,6 +35,7 @@ public class ModelGenerator {
private final String exportMacro;
private final String namespace;
private final Protocol protocol;
+ private final ProtocolTraits protocolTraits;
public ModelGenerator(Model model, ServiceShape service, CppWriterDelegator writerDelegator,
String serviceName, String smithyServiceName,
@@ -46,243 +48,40 @@ public ModelGenerator(Model model, ServiceShape service, CppWriterDelegator writ
this.exportMacro = exportMacro;
this.namespace = namespace;
this.protocol = ProtocolResolver.resolve(service, model);
+ this.protocolTraits = ProtocolResolver.traitsFor(this.protocol);
}
public void generateAll() {
- ClassifiedShapes classified = ShapeClassifier.classify(model, service);
+ ClassifiedShapes classified = ShapeClassifier.classify(model, service, protocol);
+ 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, protocolTraits,
+ 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, protocolTraits,
+ 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, protocolTraits,
+ 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, protocolTraits,
+ 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/ProtocolResolver.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ProtocolResolver.java
index 9f394798547..0704643cd3a 100644
--- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ProtocolResolver.java
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ProtocolResolver.java
@@ -13,12 +13,18 @@
import software.amazon.smithy.aws.traits.protocols.RestJson1Trait;
import software.amazon.smithy.aws.traits.protocols.RestXmlTrait;
import software.amazon.smithy.protocol.traits.Rpcv2CborTrait;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol.JsonProtocolTraits;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol.ProtocolTraits;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol.QueryXmlProtocolTraits;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol.RestXmlProtocolTraits;
/**
- * Resolves the wire protocol for a Smithy service shape.
+ * Resolves the wire protocol for a Smithy service shape, and maps a resolved
+ * protocol to the {@link ProtocolTraits} strategy that owns its rendering.
*
- *
The resolved {@link Protocol} enum carries all serde-related metadata needed
- * by downstream template generators (namespace, view/value types, method names).
+ *
{@link #traitsFor} is the single protocol-to-behavior switch in the generator;
+ * downstream generators hold a {@code ProtocolTraits} and never branch on
+ * {@link Protocol} themselves.
*/
public final class ProtocolResolver {
@@ -114,4 +120,35 @@ public static Protocol resolve(ServiceShape service, Model model) {
// Default: JSON (covers RestJson1, AwsJson1_0, AwsJson1_1)
return Protocol.JSON;
}
+
+ /**
+ * Returns the rendering strategy for a resolved protocol.
+ *
+ *
This is the only place in the generator that switches on
+ * {@link Protocol}. Every other class receives a {@link ProtocolTraits} and calls
+ * it, so adding a protocol means adding a case here plus (if its C++ surface is
+ * genuinely new) one implementation class.
+ *
+ * @param protocol the protocol returned by {@link #resolve}
+ * @return the strategy that owns this protocol's serde rendering
+ * @throws UnsupportedOperationException if the protocol has no strategy
+ */
+ public static ProtocolTraits traitsFor(Protocol protocol) {
+ if (protocol == null) {
+ throw new UnsupportedOperationException("Cannot resolve ProtocolTraits for a null protocol");
+ }
+ switch (protocol) {
+ case JSON:
+ case CBOR:
+ return new JsonProtocolTraits(protocol);
+ case REST_XML:
+ return new RestXmlProtocolTraits();
+ case QUERY_XML:
+ case EC2:
+ return new QueryXmlProtocolTraits(protocol);
+ default:
+ throw new UnsupportedOperationException(
+ "No ProtocolTraits implementation for protocol: " + protocol);
+ }
+ }
}
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
deleted file mode 100644
index d17bdf26de2..00000000000
--- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/SerdeStub.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/**
- * 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 software.amazon.smithy.model.Model;
-import software.amazon.smithy.model.shapes.StructureShape;
-
-/**
- * Generates protocol-specific serde method declarations (header) and empty stub
- * implementations (source). The stubs will be replaced by schema-based serde.
- */
-public final class SerdeStub {
-
- private SerdeStub() {
- }
-
- public static void renderHeaderDeclarations(CppWriter writer, Protocol protocol,
- String exportMacro, String className) {
- 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);
- 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);
- 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);
- 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 {
- throw new UnsupportedOperationException("Unsupported protocol for serde header declarations: " + protocol);
- }
- }
-
- public static void renderSourceImplementation(CppWriter writer, StructureShape shape,
- Model model, Protocol protocol,
- String className, String serviceName,
- String projectName) {
- 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 implementation: " + protocol);
- }
- }
-
- private static void renderJsonStub(CppWriter writer, String className) {
- writer.write("$L::$L(JsonView jsonValue) { *this = jsonValue; }", className, className);
- writer.write("");
- writer.openBlock("$L& $L::operator=(JsonView jsonValue) {", "}", className, className, () -> {
- writer.write("return *this;");
- });
- writer.write("");
- writer.openBlock("JsonValue $L::Jsonize() const {", "}", className, () -> {
- writer.write("JsonValue payload;");
- writer.write("return payload;");
- });
- }
-
- private static void renderXmlStub(CppWriter writer, String className) {
- writer.write("$L::$L(const XmlNode& xmlNode) { *this = xmlNode; }", className, className);
- writer.write("");
- writer.openBlock("$L& $L::operator=(const XmlNode& xmlNode) {", "}", className, className, () -> {
- writer.write("return *this;");
- });
- writer.write("");
- writer.openBlock("void $L::AddToNode(XmlNode& parentNode) const {", "}", className, () -> {
- });
- }
-
- private static void renderQueryXmlStub(CppWriter writer, String className) {
- writer.write("$L::$L(const XmlNode& xmlNode) { *this = xmlNode; }", className, className);
- writer.write("");
- writer.openBlock("$L& $L::operator=(const XmlNode& xmlNode) {", "}", className, className, () -> {
- writer.write("return *this;");
- });
- writer.write("");
- writer.openBlock("void $L::OutputToStream(Aws::OStream& ostream, const char* location, unsigned index, const char* locationValue) const {", "}", className, () -> {
- });
- writer.write("");
- writer.openBlock("void $L::OutputToStream(Aws::OStream& ostream, const char* location) const {", "}", className, () -> {
- });
- }
-}
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..85c1c6da40a 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,
@@ -66,20 +94,21 @@ private ShapeClassifier() {}
/**
* Classifies all shapes reachable from the given service into generation buckets.
*
- * @param model the Smithy model
- * @param service the service shape whose operations define the root set
+ * @param model the Smithy model
+ * @param service the service shape whose operations define the root set
+ * @param protocol the already-resolved protocol; passed in rather than re-resolved so
+ * that {@code ModelGenerator} remains the single resolution point
* @return classified shapes grouped by generation bucket
*/
- public static ClassifiedShapes classify(Model model, ServiceShape service) {
- Protocol protocol = ProtocolResolver.resolve(service, model);
+ public static ClassifiedShapes classify(Model model, ServiceShape service, Protocol protocol) {
TopDownIndex index = TopDownIndex.of(model);
Set reachable = GlobalTransforms.computeReachableShapes(model, 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 +120,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 +138,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 +193,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/protocol/JsonProtocolTraits.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/JsonProtocolTraits.java
new file mode 100644
index 00000000000..5d2bb4ad828
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/JsonProtocolTraits.java
@@ -0,0 +1,110 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriter;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver.Protocol;
+
+/**
+ * JSON-flavored serde rendering. Serves both {@link Protocol#JSON} and
+ * {@link Protocol#CBOR}, which share an identical C++ surface today (CBOR reuses the
+ * JSON model types and differs only on the wire).
+ */
+public final class JsonProtocolTraits implements ProtocolTraits {
+
+ private final Protocol protocol;
+
+ public JsonProtocolTraits(Protocol protocol) {
+ if (protocol != Protocol.JSON && protocol != Protocol.CBOR) {
+ throw new IllegalArgumentException(
+ "JsonProtocolTraits only serves JSON and CBOR, got: " + protocol);
+ }
+ this.protocol = protocol;
+ }
+
+ @Override
+ public Protocol protocol() {
+ return protocol;
+ }
+
+ @Override
+ public String serdeNamespace() {
+ return protocol.getSerdeNamespace();
+ }
+
+ @Override
+ public void writeShapeForwardDeclarations(CppWriter writer) {
+ writer.writeNamespaceOpen("Utils");
+ writer.writeNamespaceOpen("Json");
+ writer.write("class JsonValue;");
+ writer.write("class JsonView;");
+ writer.writeNamespaceClose("Json");
+ writer.writeNamespaceClose("Utils");
+ }
+
+ @Override
+ public void writeResultForwardDeclarations(CppWriter writer) {
+ writer.writeNamespaceOpen("Utils");
+ writer.writeNamespaceOpen("Json");
+ writer.write("class JsonValue;");
+ writer.writeNamespaceClose("Json");
+ writer.writeNamespaceClose("Utils");
+ }
+
+ @Override
+ public void writeSerdeMethodDecls(CppWriter writer, String exportMacro, String className,
+ Runnable beforeSerializeMethod) {
+ writer.write("$L $L() = default;", exportMacro, className);
+ writer.write("$L $L($L jsonValue);", exportMacro, className, protocol.getViewType());
+ writer.write("$L $L& operator=($L jsonValue);", exportMacro, className, protocol.getViewType());
+ if (beforeSerializeMethod != null) {
+ beforeSerializeMethod.run();
+ }
+ writer.write("$L $L $L() const;", exportMacro, protocol.getValueType(),
+ protocol.getSerializeMethodName());
+ }
+
+ @Override
+ public void writeResultSerdeDecls(CppWriter writer, String exportMacro, String className) {
+ writer.write("$L $L(const Aws::AmazonWebServiceResult<$L>& result);",
+ exportMacro, className, protocol.getValueType());
+ writer.write("$L $L& operator=(const Aws::AmazonWebServiceResult<$L>& result);",
+ exportMacro, className, protocol.getValueType());
+ }
+
+ @Override
+ public void writeSerdeInclude(CppWriter writer) {
+ writer.write("#include ");
+ }
+
+ @Override
+ public void writeSerdeUsingDeclarations(CppWriter writer) {
+ writer.write("using namespace Aws::Utils::Json;");
+ writer.write("using namespace Aws::Utils;");
+ }
+
+ @Override
+ public void writeSerdeMethodImpls(CppWriter writer, String className) {
+ writer.write("$L::$L(JsonView jsonValue) { *this = jsonValue; }", className, className);
+ writer.write("");
+ writer.openBlock("$L& $L::operator=(JsonView jsonValue) {", "}", className, className, () -> {
+ writer.write("return *this;");
+ });
+ writer.write("");
+ writer.openBlock("JsonValue $L::Jsonize() const {", "}", className, () -> {
+ writer.write("JsonValue payload;");
+ writer.write("return payload;");
+ });
+ }
+
+ @Override
+ public void writeResultSerdeImpls(CppWriter writer, String className) {
+ 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;"));
+ }
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/ProtocolTraits.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/ProtocolTraits.java
new file mode 100644
index 00000000000..bd9fda2bac4
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/ProtocolTraits.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriter;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver.Protocol;
+
+/**
+ * Owns every protocol-specific rendering decision for generated model code.
+ *
+ * Renderers receive a {@code ProtocolTraits} and call these methods; they never
+ * branch on {@link Protocol} themselves. One implementation exists per serde
+ * family (JSON-like, REST-XML, query-XML), not per protocol, so the
+ * conditional arms that used to be repeated across renderers are now classes.
+ *
+ *
Obtain an instance from
+ * {@code ProtocolResolver.traitsFor(ProtocolResolver.resolve(service, model))}.
+ */
+public interface ProtocolTraits {
+
+ /** The protocol this instance was resolved for. */
+ Protocol protocol();
+
+ /** C++ namespace holding the serde utilities, e.g. {@code Aws::Utils::Json}. */
+ String serdeNamespace();
+
+ // ------------------------------------------------------------------
+ // Header side
+ // ------------------------------------------------------------------
+
+ /**
+ * Forward-declares the serde types a shape (sub-object / request / event) header
+ * needs: both the value and the view type for JSON-like protocols, the node type
+ * for XML-like ones. Emits its own {@code Utils}/{@code Json}|{@code Xml}
+ * namespace nesting; the caller has already opened {@code Aws}.
+ */
+ void writeShapeForwardDeclarations(CppWriter writer);
+
+ /**
+ * Forward-declares the serde types a result header needs. This differs
+ * from {@link #writeShapeForwardDeclarations}: results reference the document
+ * type ({@code JsonValue} / {@code XmlDocument}) and never the view type.
+ */
+ void writeResultForwardDeclarations(CppWriter writer);
+
+ /**
+ * Emits the serde method declarations for a model class: the default ctor, the
+ * deserializing ctor, {@code operator=}, and the serialize method
+ * ({@code Jsonize} / {@code AddToNode} / {@code OutputToStream}).
+ *
+ * @param beforeSerializeMethod optional hook run after the deserializing ctor and
+ * {@code operator=} but before the serialize method, used to inject extra
+ * constructors at the mainline position. Ignored when {@code null}.
+ */
+ void writeSerdeMethodDecls(CppWriter writer, String exportMacro, String className,
+ Runnable beforeSerializeMethod);
+
+ /** Emits a result class's {@code AmazonWebServiceResult<...>} ctor and {@code operator=} declarations. */
+ void writeResultSerdeDecls(CppWriter writer, String exportMacro, String className);
+
+ // ------------------------------------------------------------------
+ // Source side
+ // ------------------------------------------------------------------
+
+ /** Emits the {@code #include} for this protocol's serializer header. */
+ void writeSerdeInclude(CppWriter writer);
+
+ /** Emits the {@code using namespace} declarations this protocol's serde bodies need. */
+ void writeSerdeUsingDeclarations(CppWriter writer);
+
+ /**
+ * Emits the serde method bodies matching {@link #writeSerdeMethodDecls}.
+ * Bodies are currently empty stubs pending schema-based serde.
+ */
+ void writeSerdeMethodImpls(CppWriter writer, String className);
+
+ /** Emits the result ctor / {@code operator=} bodies matching {@link #writeResultSerdeDecls}. */
+ void writeResultSerdeImpls(CppWriter writer, String className);
+
+ // ------------------------------------------------------------------
+ // Protocol-agnostic today; kept here so callers have one place to look.
+ // ------------------------------------------------------------------
+
+ /**
+ * Emits a placeholder for an event-stream event case body: a TODO marker plus a
+ * compilable callback invocation with a default-constructed event.
+ */
+ default void writeEventPayloadDecode(CppWriter writer, String eventShapeName,
+ String callbackMember) {
+ writer.write("// TODO: protocol-specific event payload deserialization");
+ writer.write("$L($L{});", callbackMember, eventShapeName);
+ }
+
+ /**
+ * Emits a placeholder in place of the protocol-specific error-payload parse. The
+ * header-derived error code/message still flow to MarshallError at the call site.
+ */
+ default void writeErrorPayloadParse(CppWriter writer) {
+ writer.write("// TODO: protocol-specific error payload deserialization");
+ }
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/QueryXmlProtocolTraits.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/QueryXmlProtocolTraits.java
new file mode 100644
index 00000000000..26de9170478
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/QueryXmlProtocolTraits.java
@@ -0,0 +1,113 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriter;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver.Protocol;
+
+/**
+ * Query-XML serde rendering. Serves both {@link Protocol#QUERY_XML} and
+ * {@link Protocol#EC2}: shapes serialize into a flat, indexed query string via two
+ * {@code OutputToStream} overloads rather than into a node tree.
+ */
+public final class QueryXmlProtocolTraits implements ProtocolTraits {
+
+ private final Protocol protocol;
+
+ public QueryXmlProtocolTraits(Protocol protocol) {
+ if (protocol != Protocol.QUERY_XML && protocol != Protocol.EC2) {
+ throw new IllegalArgumentException(
+ "QueryXmlProtocolTraits only serves QUERY_XML and EC2, got: " + protocol);
+ }
+ this.protocol = protocol;
+ }
+
+ @Override
+ public Protocol protocol() {
+ return protocol;
+ }
+
+ @Override
+ public String serdeNamespace() {
+ return protocol.getSerdeNamespace();
+ }
+
+ @Override
+ public void writeShapeForwardDeclarations(CppWriter writer) {
+ writer.writeNamespaceOpen("Utils");
+ writer.writeNamespaceOpen("Xml");
+ writer.write("class XmlNode;");
+ writer.writeNamespaceClose("Xml");
+ writer.writeNamespaceClose("Utils");
+ }
+
+ @Override
+ public void writeResultForwardDeclarations(CppWriter writer) {
+ writer.writeNamespaceOpen("Utils");
+ writer.writeNamespaceOpen("Xml");
+ writer.write("class XmlDocument;");
+ writer.writeNamespaceClose("Xml");
+ writer.writeNamespaceClose("Utils");
+ }
+
+ @Override
+ public void writeSerdeMethodDecls(CppWriter writer, String exportMacro, String className,
+ Runnable beforeSerializeMethod) {
+ 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);
+ }
+
+ @Override
+ public void writeResultSerdeDecls(CppWriter writer, String exportMacro, String className) {
+ writer.write("$L $L(const Aws::AmazonWebServiceResult& result);",
+ exportMacro, className);
+ writer.write("$L $L& operator=(const Aws::AmazonWebServiceResult& result);",
+ exportMacro, className);
+ }
+
+ @Override
+ public void writeSerdeInclude(CppWriter writer) {
+ writer.write("#include ");
+ }
+
+ @Override
+ public void writeSerdeUsingDeclarations(CppWriter writer) {
+ writer.write("using namespace Aws::Utils::Xml;");
+ writer.write("using namespace Aws::Utils;");
+ }
+
+ @Override
+ public void writeSerdeMethodImpls(CppWriter writer, String className) {
+ writer.write("$L::$L(const XmlNode& xmlNode) { *this = xmlNode; }", className, className);
+ writer.write("");
+ writer.openBlock("$L& $L::operator=(const XmlNode& xmlNode) {", "}", className, className, () -> {
+ writer.write("return *this;");
+ });
+ writer.write("");
+ writer.openBlock("void $L::OutputToStream(Aws::OStream& ostream, const char* location, "
+ + "unsigned index, const char* locationValue) const {", "}", className, () -> {
+ });
+ writer.write("");
+ writer.openBlock("void $L::OutputToStream(Aws::OStream& ostream, const char* location) const {",
+ "}", className, () -> {
+ });
+ }
+
+ @Override
+ public void writeResultSerdeImpls(CppWriter writer, String className) {
+ 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;"));
+ }
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/RestXmlProtocolTraits.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/RestXmlProtocolTraits.java
new file mode 100644
index 00000000000..06fc3f7cec8
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/main/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/RestXmlProtocolTraits.java
@@ -0,0 +1,95 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriter;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver.Protocol;
+
+/**
+ * REST-XML serde rendering. Shapes serialize by attaching themselves to a parent
+ * node via {@code AddToNode}.
+ */
+public final class RestXmlProtocolTraits implements ProtocolTraits {
+
+ @Override
+ public Protocol protocol() {
+ return Protocol.REST_XML;
+ }
+
+ @Override
+ public String serdeNamespace() {
+ return Protocol.REST_XML.getSerdeNamespace();
+ }
+
+ @Override
+ public void writeShapeForwardDeclarations(CppWriter writer) {
+ writer.writeNamespaceOpen("Utils");
+ writer.writeNamespaceOpen("Xml");
+ writer.write("class XmlNode;");
+ writer.writeNamespaceClose("Xml");
+ writer.writeNamespaceClose("Utils");
+ }
+
+ @Override
+ public void writeResultForwardDeclarations(CppWriter writer) {
+ writer.writeNamespaceOpen("Utils");
+ writer.writeNamespaceOpen("Xml");
+ writer.write("class XmlDocument;");
+ writer.writeNamespaceClose("Xml");
+ writer.writeNamespaceClose("Utils");
+ }
+
+ @Override
+ public void writeSerdeMethodDecls(CppWriter writer, String exportMacro, String className,
+ Runnable beforeSerializeMethod) {
+ 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);
+ }
+
+ @Override
+ public void writeResultSerdeDecls(CppWriter writer, String exportMacro, String className) {
+ writer.write("$L $L(const Aws::AmazonWebServiceResult& result);",
+ exportMacro, className);
+ writer.write("$L $L& operator=(const Aws::AmazonWebServiceResult& result);",
+ exportMacro, className);
+ }
+
+ @Override
+ public void writeSerdeInclude(CppWriter writer) {
+ writer.write("#include ");
+ }
+
+ @Override
+ public void writeSerdeUsingDeclarations(CppWriter writer) {
+ writer.write("using namespace Aws::Utils::Xml;");
+ writer.write("using namespace Aws::Utils;");
+ }
+
+ @Override
+ public void writeSerdeMethodImpls(CppWriter writer, String className) {
+ writer.write("$L::$L(const XmlNode& xmlNode) { *this = xmlNode; }", className, className);
+ writer.write("");
+ writer.openBlock("$L& $L::operator=(const XmlNode& xmlNode) {", "}", className, className, () -> {
+ writer.write("return *this;");
+ });
+ writer.write("");
+ writer.openBlock("void $L::AddToNode(XmlNode& parentNode) const {", "}", className, () -> {
+ });
+ }
+
+ @Override
+ public void writeResultSerdeImpls(CppWriter writer, String className) {
+ 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;"));
+ }
+}
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..da3836bd54b
--- /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,563 @@
+/**
+ * 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.protocol.ProtocolTraits;
+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 ProtocolTraits}.
+ */
+public final class EventStreamRenderer implements ShapeRenderer {
+
+ private final List eventStreams;
+ private final Model model;
+ private final ServiceShape service;
+ private final ProtocolTraits protocolTraits;
+ private final String namespace;
+ private final String exportMacro;
+ private final String smithyServiceName;
+
+ public EventStreamRenderer(List eventStreams, Model model, ServiceShape service,
+ ProtocolTraits protocolTraits, String namespace, String exportMacro,
+ String smithyServiceName) {
+ this.eventStreams = eventStreams;
+ this.model = model;
+ this.service = service;
+ this.protocolTraits = protocolTraits;
+ 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), () -> {
+ protocolTraits.writeEventPayloadDecode(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");
+ protocolTraits.writeErrorPayloadParse(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");
+ });
+ }
+
+ // ---- Initial response / event stream union ------------------------------
+
+ 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");
+ protocolTraits.writeShapeForwardDeclarations(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).
+ protocolTraits.writeSerdeMethodDecls(writer, 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 -> {
+ protocolTraits.writeSerdeInclude(writer);
+ writer.write("#include ");
+ writer.write("#include ", smithyServiceName, className);
+ writer.write("");
+ writer.write("using namespace Aws::$1L::Model;", namespace);
+ protocolTraits.writeSerdeUsingDeclarations(writer);
+ writer.write("");
+ protocolTraits.writeSerdeMethodImpls(writer, className);
+ writer.write("");
+ writer.openBlock("$1L::$1L(const Http::HeaderValueCollection& responseHeaders) {", "}", className, () -> {
+ writer.write("AWS_UNREFERENCED_PARAM(responseHeaders);");
+ });
+ });
+ }
+
+ 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");
+ protocolTraits.writeShapeForwardDeclarations(writer);
+ writer.writeNamespaceOpen(namespace);
+ writer.writeNamespaceOpen("Model");
+ writer.write("");
+ MemberRenderer.renderClassDocComment(writer, union, smithyServiceName, service.getVersion());
+ writer.openBlock("class $L {", "};", className, () -> {
+ writer.write("public:");
+ protocolTraits.writeSerdeMethodDecls(writer, exportMacro, className, null);
+ 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..0aa76dd779d
--- /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,285 @@
+/**
+ * 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.protocol.ProtocolTraits;
+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 ProtocolTraits protocolTraits;
+ private final String namespace;
+ private final String exportMacro;
+ private final String smithyServiceName;
+
+ public RequestRenderer(List requests, Model model, ServiceShape service,
+ ProtocolTraits protocolTraits, String namespace, String exportMacro,
+ String smithyServiceName) {
+ this.requests = requests;
+ this.model = model;
+ this.service = service;
+ this.protocolTraits = protocolTraits;
+ 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 -> {
+
+ protocolTraits.writeSerdeInclude(writer);
+ writer.write("#include ", smithyServiceName, className);
+ writer.write("");
+ writer.write("#include ");
+ writer.write("");
+
+ writer.write("using namespace Aws::$L::Model;", namespace);
+ protocolTraits.writeSerdeUsingDeclarations(writer);
+ 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..726f16d4f9f
--- /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,159 @@
+/**
+ * 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.CppTypeMapper;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.MemberRenderer;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol.ProtocolTraits;
+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 ProtocolTraits protocolTraits;
+ private final String namespace;
+ private final String exportMacro;
+ private final String smithyServiceName;
+
+ public ResultRenderer(List results, Model model, ServiceShape service,
+ ProtocolTraits protocolTraits, String namespace, String exportMacro,
+ String smithyServiceName) {
+ this.results = results;
+ this.model = model;
+ this.service = service;
+ this.protocolTraits = protocolTraits;
+ 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("");
+ protocolTraits.writeResultForwardDeclarations(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);
+ protocolTraits.writeResultSerdeDecls(writer, 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 ");
+ protocolTraits.writeSerdeInclude(writer);
+ writer.write("#include ");
+ writer.write("#include ", smithyServiceName, className);
+ writer.write("");
+ writer.write("#include ");
+ writer.write("");
+ writer.write("using namespace Aws::$L::Model;", namespace);
+ protocolTraits.writeSerdeUsingDeclarations(writer);
+ writer.write("using namespace Aws;");
+ writer.write("");
+
+ protocolTraits.writeResultSerdeImpls(writer, className);
+ writer.write("");
+ });
+ }
+}
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..ab491742286
--- /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,138 @@
+/**
+ * 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.protocol.ProtocolTraits;
+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 ProtocolTraits protocolTraits;
+ private final String namespace;
+ private final String exportMacro;
+ private final String serviceName;
+ private final String smithyServiceName;
+
+ public SubObjectRenderer(List subObjects, Model model, ServiceShape service,
+ ProtocolTraits protocolTraits, String namespace, String exportMacro,
+ String serviceName, String smithyServiceName) {
+ this.subObjects = subObjects;
+ this.model = model;
+ this.service = service;
+ this.protocolTraits = protocolTraits;
+ 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");
+ protocolTraits.writeShapeForwardDeclarations(writer);
+ writer.writeNamespaceOpen(namespace);
+ writer.writeNamespaceOpen("Model");
+ writer.write("");
+
+ MemberRenderer.renderClassDocComment(writer, shape, smithyServiceName, service.getVersion());
+
+ writer.openBlock("class $L {", "};", className, () -> {
+ writer.write("public:");
+ protocolTraits.writeSerdeMethodDecls(writer, exportMacro, className, null);
+ 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 -> {
+
+ protocolTraits.writeSerdeInclude(writer);
+ writer.write("#include ", smithyServiceName, className);
+ writer.write("");
+ writer.write("#include ");
+ writer.write("");
+
+ protocolTraits.writeSerdeUsingDeclarations(writer);
+ writer.write("");
+
+ writer.writeNamespaceOpen("Aws");
+ writer.writeNamespaceOpen(namespace);
+ writer.writeNamespaceOpen("Model");
+ writer.write("");
+
+ protocolTraits.writeSerdeMethodImpls(writer, className);
+ writer.write("");
+
+ writer.writeNamespaceClose("Model");
+ writer.writeNamespaceClose(namespace);
+ writer.writeNamespaceClose("Aws");
+ });
+ }
+
+}
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..07423bc434a
--- /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,184 @@
+/**
+ * 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.ProtocolResolver.Protocol;
+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);
+ Protocol protocol = ProtocolResolver.resolve(service, model);
+ EventStreamRenderer renderer = new EventStreamRenderer(
+ ShapeClassifier.classify(model, service, protocol).eventStreamHandlers(),
+ model, service, ProtocolResolver.traitsFor(protocol),
+ "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_hasHeaderCtorDefinition() {
+ 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/ProtocolResolverTest.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ProtocolResolverTest.java
index cc0f6711a13..354df389ede 100644
--- a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ProtocolResolverTest.java
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ProtocolResolverTest.java
@@ -174,4 +174,42 @@ void protocol_ec2_isXmlLike() {
assertTrue(Protocol.EC2.isXmlLike());
assertFalse(Protocol.EC2.isJsonLike());
}
+
+ // ---------- traitsFor: the single protocol -> strategy selection point ----------
+
+ @Test
+ void traitsFor_jsonAndCbor_returnJsonTraits() {
+ assertInstanceOf(com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol
+ .JsonProtocolTraits.class, ProtocolResolver.traitsFor(Protocol.JSON));
+ assertInstanceOf(com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol
+ .JsonProtocolTraits.class, ProtocolResolver.traitsFor(Protocol.CBOR));
+ }
+
+ @Test
+ void traitsFor_restXml_returnsRestXmlTraits() {
+ assertInstanceOf(com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol
+ .RestXmlProtocolTraits.class, ProtocolResolver.traitsFor(Protocol.REST_XML));
+ }
+
+ @Test
+ void traitsFor_queryAndEc2_returnQueryXmlTraits() {
+ assertInstanceOf(com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol
+ .QueryXmlProtocolTraits.class, ProtocolResolver.traitsFor(Protocol.QUERY_XML));
+ assertInstanceOf(com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol
+ .QueryXmlProtocolTraits.class, ProtocolResolver.traitsFor(Protocol.EC2));
+ }
+
+ /** Every protocol must have a strategy, and each must report its own identity back. */
+ @Test
+ void traitsFor_coversEveryProtocol_andPreservesIdentity() {
+ for (Protocol p : Protocol.values()) {
+ assertEquals(p, ProtocolResolver.traitsFor(p).protocol(),
+ "traitsFor(" + p + ") returned traits reporting a different protocol");
+ }
+ }
+
+ @Test
+ void traitsFor_rejectsNull() {
+ assertThrows(UnsupportedOperationException.class, () -> ProtocolResolver.traitsFor(null));
+ }
}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ProtocolTraitsCharacterizationTest.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ProtocolTraitsCharacterizationTest.java
new file mode 100644
index 00000000000..82e38cd3221
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/ProtocolTraitsCharacterizationTest.java
@@ -0,0 +1,308 @@
+/**
+ * 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.ProtocolResolver.Protocol;
+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 com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol.ProtocolTraits;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+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.traits.Trait;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Characterization tests pinning the exact generated C++ text for every supported
+ * protocol, so that changes to the model renderers or their {@link ProtocolTraits}
+ * strategies cannot silently alter generated output.
+ *
+ * A failure here means generated output changed. Unless the change is intentional,
+ * fix the production code rather than the assertion; if it is intentional, update the
+ * assertion in the same commit that changes the renderer.
+ */
+class ProtocolTraitsCharacterizationTest {
+
+ // ---------- fixtures ----------
+
+ /** Returns the Smithy protocol trait that makes ProtocolResolver resolve to {@code p}. */
+ private static Trait traitFor(Protocol p) {
+ switch (p) {
+ case JSON:
+ return software.amazon.smithy.aws.traits.protocols.RestJson1Trait.builder().build();
+ case CBOR:
+ return software.amazon.smithy.protocol.traits.Rpcv2CborTrait.builder().build();
+ case REST_XML:
+ return software.amazon.smithy.aws.traits.protocols.RestXmlTrait.builder().build();
+ case QUERY_XML:
+ return new software.amazon.smithy.aws.traits.protocols.AwsQueryTrait();
+ case EC2:
+ return new software.amazon.smithy.aws.traits.protocols.Ec2QueryTrait();
+ default:
+ throw new IllegalArgumentException("Unhandled protocol: " + p);
+ }
+ }
+
+ /**
+ * A minimal service with one operation whose input and output each reference a
+ * nested structure, so SubObject / Request / Result renderers all have work to do.
+ */
+ private static Model modelFor(Protocol p) {
+ StringShape str = StringShape.builder().id("com.example#Str").build();
+ StructureShape nested = StructureShape.builder()
+ .id("com.example#Nested")
+ .addMember("value", str.getId())
+ .build();
+ StructureShape input = StructureShape.builder()
+ .id("com.example#DoThingInput")
+ .addMember("name", str.getId())
+ .addMember("nested", nested.getId())
+ .build();
+ StructureShape output = StructureShape.builder()
+ .id("com.example#DoThingOutput")
+ .addMember("nested", nested.getId())
+ .build();
+ OperationShape op = OperationShape.builder()
+ .id("com.example#DoThing")
+ .input(input.getId())
+ .output(output.getId())
+ .build();
+ ServiceShape service = ServiceShape.builder()
+ .id("com.example#Example")
+ .version("2024-01-01")
+ .addTrait(traitFor(p))
+ .addOperation(op.getId())
+ .build();
+ return Model.builder().addShapes(str, nested, input, output, op, service).build();
+ }
+
+ private static ServiceShape serviceOf(Model model) {
+ return model.expectShape(ShapeId.from("com.example#Example"), ServiceShape.class);
+ }
+
+ /** Renders every model file for the protocol and returns file-name -> contents. */
+ private static java.util.Map renderAll(Protocol p) {
+ Model model = modelFor(p);
+ ServiceShape service = serviceOf(model);
+ Protocol resolved = ProtocolResolver.resolve(service, model);
+ assertEquals(p, resolved, "Fixture trait did not resolve to the intended protocol");
+
+ ShapeClassifier.ClassifiedShapes classified = ShapeClassifier.classify(model, service, resolved);
+ MockManifest manifest = new MockManifest();
+ CppWriterDelegator delegator = new CppWriterDelegator(manifest);
+
+ ProtocolTraits traits = ProtocolResolver.traitsFor(resolved);
+ new SubObjectRenderer(classified.subObjects(), model, service, traits,
+ "Example", "AWS_EXAMPLE_API", "Example", "example").render(delegator);
+ new RequestRenderer(classified.requests(), model, service, traits,
+ "Example", "AWS_EXAMPLE_API", "example").render(delegator);
+ new ResultRenderer(classified.results(), model, service, traits,
+ "Example", "AWS_EXAMPLE_API", "example").render(delegator);
+
+ delegator.flushWriters();
+
+ java.util.Map out = new java.util.TreeMap<>();
+ for (java.nio.file.Path path : manifest.getFiles()) {
+ String name = path.getFileName().toString();
+ out.put(name, manifest.getFileString(path).orElseThrow());
+ }
+ return out;
+ }
+
+ private static String file(Protocol p, String fileName) {
+ java.util.Map files = renderAll(p);
+ String content = files.get(fileName);
+ assertTrue(content != null,
+ "Expected generated file " + fileName + " for " + p + "; got " + files.keySet());
+ return content;
+ }
+
+ // ---------- every protocol produces the same file set ----------
+
+ @ParameterizedTest
+ @EnumSource(Protocol.class)
+ void everyProtocol_generatesTheSameFileSet(Protocol p) {
+ assertEquals(
+ java.util.Set.of("Nested.h", "Nested.cpp",
+ "DoThingRequest.h", "DoThingRequest.cpp",
+ "DoThingResult.h", "DoThingResult.cpp"),
+ renderAll(p).keySet(),
+ "File set drifted for " + p);
+ }
+
+ // ---------- sub-object: serde decls, includes, usings, fwd decls ----------
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"JSON", "CBOR"})
+ void jsonLike_subObjectHeader_hasJsonSerdeDeclsAndFwdDecls(Protocol p) {
+ String h = file(p, "Nested.h");
+ assertTrue(h.contains("AWS_EXAMPLE_API Nested() = default;"), h);
+ assertTrue(h.contains("AWS_EXAMPLE_API Nested(Aws::Utils::Json::JsonView jsonValue);"), h);
+ assertTrue(h.contains("AWS_EXAMPLE_API Nested& operator=(Aws::Utils::Json::JsonView jsonValue);"), h);
+ assertTrue(h.contains("AWS_EXAMPLE_API Aws::Utils::Json::JsonValue Jsonize() const;"), h);
+ assertTrue(h.contains("class JsonValue;"), h);
+ assertTrue(h.contains("class JsonView;"), h);
+ assertFalse(h.contains("XmlNode"), "JSON-like header must not mention XML: " + h);
+ }
+
+ @Test
+ void restXml_subObjectHeader_hasAddToNodeAndXmlNodeFwdDecl() {
+ String h = file(Protocol.REST_XML, "Nested.h");
+ assertTrue(h.contains("AWS_EXAMPLE_API Nested(const Aws::Utils::Xml::XmlNode& xmlNode);"), h);
+ assertTrue(h.contains("AWS_EXAMPLE_API Nested& operator=(const Aws::Utils::Xml::XmlNode& xmlNode);"), h);
+ assertTrue(h.contains("AWS_EXAMPLE_API void AddToNode(Aws::Utils::Xml::XmlNode& parentNode) const;"), h);
+ assertTrue(h.contains("class XmlNode;"), h);
+ assertFalse(h.contains("Jsonize"), "REST_XML header must not mention Jsonize: " + h);
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"QUERY_XML", "EC2"})
+ void queryLike_subObjectHeader_hasBothOutputToStreamOverloads(Protocol p) {
+ String h = file(p, "Nested.h");
+ assertTrue(h.contains("AWS_EXAMPLE_API Nested(const Aws::Utils::Xml::XmlNode& xmlNode);"), h);
+ assertTrue(h.contains("AWS_EXAMPLE_API void OutputToStream(Aws::OStream& ostream, "
+ + "const char* location, unsigned index, const char* locationValue) const;"), h);
+ assertTrue(h.contains("AWS_EXAMPLE_API void OutputToStream(Aws::OStream& ostream, "
+ + "const char* location) const;"), h);
+ assertFalse(h.contains("AddToNode"), "Query-like header must not use AddToNode: " + h);
+ assertFalse(h.contains("Jsonize"), "Query-like header must not mention Jsonize: " + h);
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"JSON", "CBOR"})
+ void jsonLike_subObjectSource_hasJsonIncludeUsingAndStubs(Protocol p) {
+ String c = file(p, "Nested.cpp");
+ assertTrue(c.contains("#include "), c);
+ assertTrue(c.contains("using namespace Aws::Utils::Json;"), c);
+ assertTrue(c.contains("using namespace Aws::Utils;"), c);
+ assertTrue(c.contains("Nested::Nested(JsonView jsonValue) { *this = jsonValue; }"), c);
+ assertTrue(c.contains("Nested& Nested::operator=(JsonView jsonValue) {"), c);
+ assertTrue(c.contains("JsonValue Nested::Jsonize() const {"), c);
+ assertTrue(c.contains("JsonValue payload;"), c);
+ assertFalse(c.contains("XmlSerializer.h"), c);
+ }
+
+ @Test
+ void restXml_subObjectSource_hasXmlIncludeUsingAndStubs() {
+ String c = file(Protocol.REST_XML, "Nested.cpp");
+ assertTrue(c.contains("#include "), c);
+ assertTrue(c.contains("using namespace Aws::Utils::Xml;"), c);
+ assertTrue(c.contains("Nested::Nested(const XmlNode& xmlNode) { *this = xmlNode; }"), c);
+ assertTrue(c.contains("Nested& Nested::operator=(const XmlNode& xmlNode) {"), c);
+ assertTrue(c.contains("void Nested::AddToNode(XmlNode& parentNode) const {"), c);
+ assertFalse(c.contains("JsonSerializer.h"), c);
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"QUERY_XML", "EC2"})
+ void queryLike_subObjectSource_hasBothOutputToStreamBodies(Protocol p) {
+ String c = file(p, "Nested.cpp");
+ assertTrue(c.contains("#include "), c);
+ assertTrue(c.contains("void Nested::OutputToStream(Aws::OStream& ostream, const char* location, "
+ + "unsigned index, const char* locationValue) const {"), c);
+ assertTrue(c.contains("void Nested::OutputToStream(Aws::OStream& ostream, "
+ + "const char* location) const {"), c);
+ }
+
+ // ---------- result: AmazonWebServiceResult<> payload type ----------
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"JSON", "CBOR"})
+ void jsonLike_resultHeader_usesJsonValuePayloadAndForwardDeclaresJsonValue(Protocol p) {
+ String h = file(p, "DoThingResult.h");
+ assertTrue(h.contains("AWS_EXAMPLE_API DoThingResult(const "
+ + "Aws::AmazonWebServiceResult& result);"), h);
+ assertTrue(h.contains("AWS_EXAMPLE_API DoThingResult& operator=(const "
+ + "Aws::AmazonWebServiceResult& result);"), h);
+ assertTrue(h.contains("class JsonValue;"), h);
+ // Result headers forward-declare only the document type, never JsonView.
+ assertFalse(h.contains("class JsonView;"),
+ "Result header must not forward-declare JsonView: " + h);
+ assertFalse(h.contains("XmlDocument"), h);
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"REST_XML", "QUERY_XML", "EC2"})
+ void xmlLike_resultHeader_usesXmlDocumentPayloadAndForwardDeclaresXmlDocument(Protocol p) {
+ String h = file(p, "DoThingResult.h");
+ assertTrue(h.contains("AWS_EXAMPLE_API DoThingResult(const "
+ + "Aws::AmazonWebServiceResult& result);"), h);
+ assertTrue(h.contains("AWS_EXAMPLE_API DoThingResult& operator=(const "
+ + "Aws::AmazonWebServiceResult& result);"), h);
+ assertTrue(h.contains("class XmlDocument;"), h);
+ assertFalse(h.contains("JsonValue"), h);
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"JSON", "CBOR"})
+ void jsonLike_resultSource_hasJsonUsingsAndCtorImpls(Protocol p) {
+ String c = file(p, "DoThingResult.cpp");
+ assertTrue(c.contains("#include "), c);
+ assertTrue(c.contains("using namespace Aws::Example::Model;"), c);
+ assertTrue(c.contains("using namespace Aws::Utils::Json;"), c);
+ assertTrue(c.contains("using namespace Aws::Utils;"), c);
+ assertTrue(c.contains("using namespace Aws;"), c);
+ assertTrue(c.contains("DoThingResult::DoThingResult(const "
+ + "Aws::AmazonWebServiceResult& result) {"), c);
+ assertTrue(c.contains("DoThingResult& DoThingResult::operator=(const "
+ + "Aws::AmazonWebServiceResult& result) {"), c);
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"REST_XML", "QUERY_XML", "EC2"})
+ void xmlLike_resultSource_hasXmlUsingsAndCtorImpls(Protocol p) {
+ String c = file(p, "DoThingResult.cpp");
+ assertTrue(c.contains("#include "), c);
+ assertTrue(c.contains("using namespace Aws::Utils::Xml;"), c);
+ assertTrue(c.contains("DoThingResult::DoThingResult(const "
+ + "Aws::AmazonWebServiceResult& result) {"), c);
+ assertTrue(c.contains("DoThingResult& DoThingResult::operator=(const "
+ + "Aws::AmazonWebServiceResult& result) {"), c);
+ }
+
+ // ---------- request: only the serde include + usings are protocol-specific ----------
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"JSON", "CBOR"})
+ void jsonLike_requestSource_hasJsonIncludeAndUsings(Protocol p) {
+ String c = file(p, "DoThingRequest.cpp");
+ assertTrue(c.contains("#include "), c);
+ assertTrue(c.contains("using namespace Aws::Example::Model;"), c);
+ assertTrue(c.contains("using namespace Aws::Utils::Json;"), c);
+ assertTrue(c.contains("using namespace Aws::Utils;"), c);
+ assertFalse(c.contains("XmlSerializer.h"), c);
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"REST_XML", "QUERY_XML", "EC2"})
+ void xmlLike_requestSource_hasXmlIncludeAndUsings(Protocol p) {
+ String c = file(p, "DoThingRequest.cpp");
+ assertTrue(c.contains("#include "), c);
+ assertTrue(c.contains("using namespace Aws::Utils::Xml;"), c);
+ assertFalse(c.contains("JsonSerializer.h"), c);
+ }
+
+ /** The request header is protocol-agnostic today; pin that so a regression is loud. */
+ @ParameterizedTest
+ @EnumSource(Protocol.class)
+ void requestHeader_isProtocolAgnostic(Protocol p) {
+ String h = file(p, "DoThingRequest.h");
+ assertTrue(h.contains("Aws::String SerializePayload() const override;"), h);
+ assertFalse(h.contains("JsonSerializer.h"), "Request header must not include serde headers: " + h);
+ assertFalse(h.contains("XmlSerializer.h"), "Request header must not include serde headers: " + h);
+ }
+}
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..d0aefbb6bb6
--- /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,130 @@
+/**
+ * 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.ProtocolResolver.Protocol;
+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);
+ Protocol protocol = ProtocolResolver.resolve(service, model);
+ RequestRenderer renderer = new RequestRenderer(
+ ShapeClassifier.classify(model, service, protocol).requests(),
+ model, service, ProtocolResolver.traitsFor(protocol),
+ "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/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..d74f17b29a7 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
@@ -51,25 +51,25 @@ private Model buildSimpleModel() {
void classifiesRequestShape() {
Model model = buildSimpleModel();
ServiceShape service = model.expectShape(ShapeId.from("com.example#TestService"), ServiceShape.class);
- var classified = ShapeClassifier.classify(model, service);
+ var classified = ShapeClassifier.classify(model, service, ProtocolResolver.resolve(service, model));
assertTrue(classified.requests().stream()
- .anyMatch(s -> s.getId().getName().equals("GetItemRequest")));
+ .anyMatch(r -> r.shape().getId().getName().equals("GetItemRequest")));
}
@Test
void classifiesResultShape() {
Model model = buildSimpleModel();
ServiceShape service = model.expectShape(ShapeId.from("com.example#TestService"), ServiceShape.class);
- var classified = ShapeClassifier.classify(model, service);
+ var classified = ShapeClassifier.classify(model, service, ProtocolResolver.resolve(service, model));
assertTrue(classified.results().stream()
- .anyMatch(s -> s.getId().getName().equals("GetItemResponse")));
+ .anyMatch(r -> r.shape().getId().getName().equals("GetItemResponse")));
}
@Test
void classifiesSubObject() {
Model model = buildSimpleModel();
ServiceShape service = model.expectShape(ShapeId.from("com.example#TestService"), ServiceShape.class);
- var classified = ShapeClassifier.classify(model, service);
+ var classified = ShapeClassifier.classify(model, service, ProtocolResolver.resolve(service, model));
assertTrue(classified.subObjects().stream()
.anyMatch(s -> s.getId().getName().equals("ItemData")));
}
@@ -97,7 +97,7 @@ void skipsDocumentShape() {
.build();
StringShape str = StringShape.builder().id("com.example#String").build();
Model model = Model.builder().addShapes(doc, request, response, op, service, str).build();
- var classified = ShapeClassifier.classify(model, service);
+ var classified = ShapeClassifier.classify(model, service, ProtocolResolver.resolve(service, model));
// Document should not appear in any list
assertTrue(classified.subObjects().stream()
.noneMatch(s -> s.getId().getName().equals("Doc")));
@@ -128,7 +128,7 @@ void skipsUnmodeledExceptionJson() {
.addTrait(software.amazon.smithy.aws.traits.protocols.RestJson1Trait.builder().build())
.build();
Model model = Model.builder().addShapes(str, exception, request, response, op, service).build();
- var classified = ShapeClassifier.classify(model, service);
+ var classified = ShapeClassifier.classify(model, service, ProtocolResolver.resolve(service, model));
assertTrue(classified.subObjects().stream()
.noneMatch(s -> s.getId().getName().equals("NotFoundException")));
}
@@ -159,7 +159,7 @@ void classifiesModeledExceptionJson() {
.addTrait(software.amazon.smithy.aws.traits.protocols.RestJson1Trait.builder().build())
.build();
Model model = Model.builder().addShapes(str, exception, request, response, op, service).build();
- var classified = ShapeClassifier.classify(model, service);
+ var classified = ShapeClassifier.classify(model, service, ProtocolResolver.resolve(service, model));
assertTrue(classified.subObjects().stream()
.anyMatch(s -> s.getId().getName().equals("DetailedException")));
}
@@ -190,7 +190,7 @@ void skipsUnmodeledExceptionXml() {
.addTrait(software.amazon.smithy.aws.traits.protocols.RestXmlTrait.builder().build())
.build();
Model model = Model.builder().addShapes(str, exception, request, response, op, service).build();
- var classified = ShapeClassifier.classify(model, service);
+ var classified = ShapeClassifier.classify(model, service, ProtocolResolver.resolve(service, model));
assertTrue(classified.subObjects().stream()
.noneMatch(s -> s.getId().getName().equals("NotFoundException")));
}
@@ -230,7 +230,7 @@ void classifiesEventStreamHandler() {
Model model = Model.builder()
.addShapes(str, eventA, eventStream, request, response, op, service)
.build();
- var classified = ShapeClassifier.classify(model, service);
+ var classified = ShapeClassifier.classify(model, service, ProtocolResolver.resolve(service, model));
// Event stream handler should be recorded
assertEquals(1, classified.eventStreamHandlers().size());
@@ -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
@@ -277,7 +334,7 @@ void classifiesEnumShape() {
Model model = Model.builder()
.addShapes(enumStr, request, response, op, service)
.build();
- var classified = ShapeClassifier.classify(model, service);
+ var classified = ShapeClassifier.classify(model, service, ProtocolResolver.resolve(service, model));
assertTrue(classified.enums().stream()
.anyMatch(s -> s.getId().getName().equals("Status")));
}
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);
+ }
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/JsonProtocolTraitsTest.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/JsonProtocolTraitsTest.java
new file mode 100644
index 00000000000..89b527be037
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/JsonProtocolTraitsTest.java
@@ -0,0 +1,126 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol;
+
+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.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class JsonProtocolTraitsTest {
+
+ private static String render(java.util.function.Consumer body) {
+ CppWriter writer = new CppWriter();
+ body.accept(writer);
+ return writer.toString();
+ }
+
+ private final ProtocolTraits json = new JsonProtocolTraits(Protocol.JSON);
+ private final ProtocolTraits cbor = new JsonProtocolTraits(Protocol.CBOR);
+
+ @Test
+ void reportsItsOwnProtocolIdentity() {
+ assertEquals(Protocol.JSON, json.protocol());
+ assertEquals(Protocol.CBOR, cbor.protocol());
+ }
+
+ @Test
+ void serdeNamespace_isJsonUtils() {
+ assertEquals("Aws::Utils::Json", json.serdeNamespace());
+ assertEquals("Aws::Utils::Json", cbor.serdeNamespace());
+ }
+
+ @Test
+ void serdeMethodDecls_emitJsonCtorAssignAndJsonize() {
+ String out = render(w -> json.writeSerdeMethodDecls(w, "AWS_EXAMPLE_API", "Nested", null));
+ assertTrue(out.contains("AWS_EXAMPLE_API Nested() = default;"), out);
+ assertTrue(out.contains("AWS_EXAMPLE_API Nested(Aws::Utils::Json::JsonView jsonValue);"), out);
+ assertTrue(out.contains("AWS_EXAMPLE_API Nested& operator=(Aws::Utils::Json::JsonView jsonValue);"), out);
+ assertTrue(out.contains("AWS_EXAMPLE_API Aws::Utils::Json::JsonValue Jsonize() const;"), out);
+ }
+
+ @Test
+ void serdeMethodDecls_runsHookBeforeSerializeMethod() {
+ CppWriter writer = new CppWriter();
+ json.writeSerdeMethodDecls(writer, "AWS_EXAMPLE_API", "Nested",
+ () -> writer.write("AWS_EXAMPLE_API Nested(const Http::HeaderValueCollection& responseHeaders);"));
+ String out = writer.toString();
+ int hook = out.indexOf("HeaderValueCollection");
+ int jsonize = out.indexOf("Jsonize() const;");
+ assertTrue(hook >= 0, "Hook output missing: " + out);
+ assertTrue(jsonize > hook, "Hook must run before the serialize method: " + out);
+ }
+
+ @Test
+ void serdeMethodImpls_emitEmptyJsonBodies() {
+ String out = render(w -> json.writeSerdeMethodImpls(w, "Nested"));
+ assertTrue(out.contains("Nested::Nested(JsonView jsonValue) { *this = jsonValue; }"), out);
+ assertTrue(out.contains("Nested& Nested::operator=(JsonView jsonValue) {"), out);
+ assertTrue(out.contains("JsonValue Nested::Jsonize() const {"), out);
+ assertTrue(out.contains("JsonValue payload;"), out);
+ assertTrue(out.contains("return payload;"), out);
+ }
+
+ @Test
+ void shapeForwardDeclarations_declareValueAndView() {
+ String out = render(json::writeShapeForwardDeclarations);
+ assertTrue(out.contains("class JsonValue;"), out);
+ assertTrue(out.contains("class JsonView;"), out);
+ assertTrue(out.contains("namespace Utils"), out);
+ assertTrue(out.contains("namespace Json"), out);
+ }
+
+ @Test
+ void resultForwardDeclarations_declareValueOnly() {
+ String out = render(json::writeResultForwardDeclarations);
+ assertTrue(out.contains("class JsonValue;"), out);
+ assertFalse(out.contains("class JsonView;"),
+ "Result headers must not forward-declare JsonView: " + out);
+ }
+
+ @Test
+ void resultSerdeDecls_useJsonValuePayload() {
+ String out = render(w -> json.writeResultSerdeDecls(w, "AWS_EXAMPLE_API", "DoThingResult"));
+ assertTrue(out.contains("AWS_EXAMPLE_API DoThingResult(const "
+ + "Aws::AmazonWebServiceResult& result);"), out);
+ assertTrue(out.contains("AWS_EXAMPLE_API DoThingResult& operator=(const "
+ + "Aws::AmazonWebServiceResult& result);"), out);
+ }
+
+ @Test
+ void resultSerdeImpls_useJsonValuePayload() {
+ String out = render(w -> json.writeResultSerdeImpls(w, "DoThingResult"));
+ assertTrue(out.contains("DoThingResult::DoThingResult(const "
+ + "Aws::AmazonWebServiceResult& result) {"), out);
+ assertTrue(out.contains("*this = result;"), out);
+ assertTrue(out.contains("DoThingResult& DoThingResult::operator=(const "
+ + "Aws::AmazonWebServiceResult& result) {"), out);
+ assertTrue(out.contains("return *this;"), out);
+ }
+
+ @Test
+ void serdeIncludeAndUsings_areJsonFlavored() {
+ assertTrue(render(json::writeSerdeInclude)
+ .contains("#include "));
+ String usings = render(json::writeSerdeUsingDeclarations);
+ assertTrue(usings.contains("using namespace Aws::Utils::Json;"), usings);
+ assertTrue(usings.contains("using namespace Aws::Utils;"), usings);
+ }
+
+ @Test
+ void payloadStubs_areProtocolAgnostic() {
+ String event = render(w -> json.writeEventPayloadDecode(w, "ShardEvent", "m_onShardEvent"));
+ assertTrue(event.contains("// TODO: protocol-specific event payload deserialization"), event);
+ assertTrue(event.contains("m_onShardEvent(ShardEvent{});"), event);
+ assertFalse(event.contains("JsonValue"), "Event stub must stay protocol-agnostic: " + event);
+
+ String err = render(json::writeErrorPayloadParse);
+ assertTrue(err.contains("// TODO: protocol-specific error payload deserialization"), err);
+ assertFalse(err.contains("JsonView"), "Error stub must stay protocol-agnostic: " + err);
+ }
+}
diff --git a/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/ProtocolTraitsSerdeTest.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/ProtocolTraitsSerdeTest.java
new file mode 100644
index 00000000000..b6100b4ca77
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/ProtocolTraitsSerdeTest.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriter;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver.Protocol;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Pins the invariant that the event- and error-payload stubs stay protocol-agnostic
+ * for EVERY protocol -- they are {@code default} methods on {@link ProtocolTraits} and
+ * no implementation should override them with protocol-specific text until
+ * schema-based serde lands.
+ */
+class ProtocolTraitsSerdeTest {
+
+ private static String render(java.util.function.Consumer body) {
+ CppWriter writer = new CppWriter();
+ body.accept(writer);
+ return writer.toString();
+ }
+
+ @ParameterizedTest
+ @EnumSource(Protocol.class)
+ void eventPayloadDecode_isProtocolAgnostic(Protocol p) {
+ ProtocolTraits traits = ProtocolResolver.traitsFor(p);
+ String out = render(w -> traits.writeEventPayloadDecode(
+ w, "SubscribeToShardEvent", "m_onSubscribeToShardEvent"));
+ assertTrue(out.contains("// TODO: protocol-specific event payload deserialization"),
+ "Missing TODO marker for " + p + ": " + out);
+ assertTrue(out.contains("m_onSubscribeToShardEvent(SubscribeToShardEvent{});"),
+ "Missing callback invocation for " + p + ": " + out);
+ assertFalse(out.contains("JsonValue"), "Should not emit JSON tokens for " + p + ": " + out);
+ assertFalse(out.contains("Cbor"), "Should not emit CBOR tokens for " + p + ": " + out);
+ assertFalse(out.contains("XmlNode"), "Should not emit XML tokens for " + p + ": " + out);
+ }
+
+ @ParameterizedTest
+ @EnumSource(Protocol.class)
+ void errorPayloadParse_isProtocolAgnostic(Protocol p) {
+ ProtocolTraits traits = ProtocolResolver.traitsFor(p);
+ String out = render(traits::writeErrorPayloadParse);
+ assertTrue(out.contains("// TODO: protocol-specific error payload deserialization"),
+ "Missing TODO marker for " + p + ": " + out);
+ assertFalse(out.contains("JsonView"), "Should not emit JSON tokens for " + p + ": " + out);
+ assertFalse(out.contains("XmlNode"), "Should not emit XML tokens for " + p + ": " + out);
+ }
+
+ /** The JSON serde method bodies are empty stubs pending schema-based serde. */
+ @Test
+ void jsonSerdeMethodImpls_emitEmptyBodies() {
+ ProtocolTraits traits = ProtocolResolver.traitsFor(Protocol.JSON);
+ String out = render(w -> traits.writeSerdeMethodImpls(w, "SubscribeToShardEventStream"));
+ 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/protocol/XmlProtocolTraitsTest.java b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/XmlProtocolTraitsTest.java
new file mode 100644
index 00000000000..be397d8dad8
--- /dev/null
+++ b/tools/code-generation/smithy/cpp-codegen/smithy-cpp-codegen/src/test/java/com/amazonaws/util/awsclientsmithygenerator/generators/model/protocol/XmlProtocolTraitsTest.java
@@ -0,0 +1,161 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+package com.amazonaws.util.awsclientsmithygenerator.generators.model.protocol;
+
+import com.amazonaws.util.awsclientsmithygenerator.generators.CppWriter;
+import com.amazonaws.util.awsclientsmithygenerator.generators.model.ProtocolResolver.Protocol;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class XmlProtocolTraitsTest {
+
+ private static String render(java.util.function.Consumer body) {
+ CppWriter writer = new CppWriter();
+ body.accept(writer);
+ return writer.toString();
+ }
+
+ private final ProtocolTraits restXml = new RestXmlProtocolTraits();
+ private final ProtocolTraits queryXml = new QueryXmlProtocolTraits(Protocol.QUERY_XML);
+ private final ProtocolTraits ec2 = new QueryXmlProtocolTraits(Protocol.EC2);
+
+ // ---------- identity ----------
+
+ @Test
+ void reportIdentityAndNamespace() {
+ assertEquals(Protocol.REST_XML, restXml.protocol());
+ assertEquals(Protocol.QUERY_XML, queryXml.protocol());
+ assertEquals(Protocol.EC2, ec2.protocol());
+ assertEquals("Aws::Utils::Xml", restXml.serdeNamespace());
+ assertEquals("Aws::Utils::Xml", queryXml.serdeNamespace());
+ assertEquals("Aws::Utils::Xml", ec2.serdeNamespace());
+ }
+
+ @Test
+ void queryXmlTraits_rejectsNonQueryProtocols() {
+ assertThrows(IllegalArgumentException.class, () -> new QueryXmlProtocolTraits(Protocol.JSON));
+ assertThrows(IllegalArgumentException.class, () -> new QueryXmlProtocolTraits(Protocol.REST_XML));
+ }
+
+ // ---------- REST_XML: AddToNode ----------
+
+ @Test
+ void restXml_serdeMethodDecls_emitAddToNode() {
+ String out = render(w -> restXml.writeSerdeMethodDecls(w, "AWS_EXAMPLE_API", "Nested", null));
+ assertTrue(out.contains("AWS_EXAMPLE_API Nested() = default;"), out);
+ assertTrue(out.contains("AWS_EXAMPLE_API Nested(const Aws::Utils::Xml::XmlNode& xmlNode);"), out);
+ assertTrue(out.contains("AWS_EXAMPLE_API Nested& operator=(const Aws::Utils::Xml::XmlNode& xmlNode);"), out);
+ assertTrue(out.contains("AWS_EXAMPLE_API void AddToNode(Aws::Utils::Xml::XmlNode& parentNode) const;"), out);
+ assertFalse(out.contains("Jsonize"), out);
+ assertFalse(out.contains("OutputToStream"), out);
+ }
+
+ @Test
+ void restXml_serdeMethodImpls_emitAddToNodeBody() {
+ String out = render(w -> restXml.writeSerdeMethodImpls(w, "Nested"));
+ assertTrue(out.contains("Nested::Nested(const XmlNode& xmlNode) { *this = xmlNode; }"), out);
+ assertTrue(out.contains("Nested& Nested::operator=(const XmlNode& xmlNode) {"), out);
+ assertTrue(out.contains("void Nested::AddToNode(XmlNode& parentNode) const {"), out);
+ }
+
+ @Test
+ void restXml_serdeMethodDecls_runsHookBeforeSerializeMethod() {
+ CppWriter writer = new CppWriter();
+ restXml.writeSerdeMethodDecls(writer, "AWS_EXAMPLE_API", "Nested",
+ () -> writer.write("AWS_EXAMPLE_API Nested(const Http::HeaderValueCollection& responseHeaders);"));
+ String out = writer.toString();
+ int hook = out.indexOf("HeaderValueCollection");
+ int serialize = out.indexOf("AddToNode");
+ assertTrue(hook >= 0 && serialize > hook,
+ "Hook must run before the serialize method: " + out);
+ }
+
+ // ---------- QUERY_XML / EC2: two OutputToStream overloads ----------
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"QUERY_XML", "EC2"})
+ void queryLike_serdeMethodDecls_emitBothOutputToStreamOverloads(Protocol p) {
+ ProtocolTraits traits = new QueryXmlProtocolTraits(p);
+ String out = render(w -> traits.writeSerdeMethodDecls(w, "AWS_EXAMPLE_API", "Nested", null));
+ assertTrue(out.contains("AWS_EXAMPLE_API Nested(const Aws::Utils::Xml::XmlNode& xmlNode);"), out);
+ assertTrue(out.contains("AWS_EXAMPLE_API void OutputToStream(Aws::OStream& ostream, "
+ + "const char* location, unsigned index, const char* locationValue) const;"), out);
+ assertTrue(out.contains("AWS_EXAMPLE_API void OutputToStream(Aws::OStream& ostream, "
+ + "const char* location) const;"), out);
+ assertFalse(out.contains("AddToNode"), out);
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"QUERY_XML", "EC2"})
+ void queryLike_serdeMethodImpls_emitBothOutputToStreamBodies(Protocol p) {
+ ProtocolTraits traits = new QueryXmlProtocolTraits(p);
+ String out = render(w -> traits.writeSerdeMethodImpls(w, "Nested"));
+ assertTrue(out.contains("Nested::Nested(const XmlNode& xmlNode) { *this = xmlNode; }"), out);
+ assertTrue(out.contains("void Nested::OutputToStream(Aws::OStream& ostream, const char* location, "
+ + "unsigned index, const char* locationValue) const {"), out);
+ assertTrue(out.contains("void Nested::OutputToStream(Aws::OStream& ostream, "
+ + "const char* location) const {"), out);
+ }
+
+ @ParameterizedTest
+ @EnumSource(value = Protocol.class, names = {"QUERY_XML", "EC2"})
+ void queryLike_serdeMethodDecls_runsHookBeforeSerializeMethod(Protocol p) {
+ ProtocolTraits traits = new QueryXmlProtocolTraits(p);
+ CppWriter writer = new CppWriter();
+ traits.writeSerdeMethodDecls(writer, "AWS_EXAMPLE_API", "Nested",
+ () -> writer.write("AWS_EXAMPLE_API Nested(const Http::HeaderValueCollection& responseHeaders);"));
+ String out = writer.toString();
+ int hook = out.indexOf("HeaderValueCollection");
+ int serialize = out.indexOf("OutputToStream");
+ assertTrue(hook >= 0 && serialize > hook,
+ "Hook must run before the serialize method: " + out);
+ }
+
+ // ---------- shared XML behavior ----------
+
+ @Test
+ void allXmlTraits_shareForwardDeclarationsIncludeAndUsings() {
+ for (ProtocolTraits traits : java.util.List.of(restXml, queryXml, ec2)) {
+ String shapeFwd = render(traits::writeShapeForwardDeclarations);
+ assertTrue(shapeFwd.contains("class XmlNode;"), shapeFwd);
+ assertFalse(shapeFwd.contains("JsonValue"), shapeFwd);
+
+ String resultFwd = render(traits::writeResultForwardDeclarations);
+ assertTrue(resultFwd.contains("class XmlDocument;"), resultFwd);
+ assertFalse(resultFwd.contains("class XmlNode;"),
+ "Result headers declare the document type, not the node type: " + resultFwd);
+
+ assertTrue(render(traits::writeSerdeInclude)
+ .contains("#include "));
+
+ String usings = render(traits::writeSerdeUsingDeclarations);
+ assertTrue(usings.contains("using namespace Aws::Utils::Xml;"), usings);
+ assertTrue(usings.contains("using namespace Aws::Utils;"), usings);
+ }
+ }
+
+ @Test
+ void allXmlTraits_useXmlDocumentForResults() {
+ for (ProtocolTraits traits : java.util.List.of(restXml, queryXml, ec2)) {
+ String decls = render(w -> traits.writeResultSerdeDecls(w, "AWS_EXAMPLE_API", "DoThingResult"));
+ assertTrue(decls.contains("AWS_EXAMPLE_API DoThingResult(const "
+ + "Aws::AmazonWebServiceResult& result);"), decls);
+ assertTrue(decls.contains("AWS_EXAMPLE_API DoThingResult& operator=(const "
+ + "Aws::AmazonWebServiceResult& result);"), decls);
+
+ String impls = render(w -> traits.writeResultSerdeImpls(w, "DoThingResult"));
+ assertTrue(impls.contains("DoThingResult::DoThingResult(const "
+ + "Aws::AmazonWebServiceResult& result) {"), impls);
+ assertTrue(impls.contains("DoThingResult& DoThingResult::operator=(const "
+ + "Aws::AmazonWebServiceResult& result) {"), impls);
+ }
+ }
+}