Repository: smithy-lang/smithy
Pull request #2059 (2023-12-04 15:59:14)

Add JReleaser config
Adds the jreleaser plugin and configuration for publishing to Maven Central. Using the plugin is configurable with a Gradle property, so we can continue to use the old release method if necessary. The `closeRepository` and `releaseRepository` props in the jreleaser config are set to false right now so we can check the staged artifacts before actually releasing, but can be changed later to automatically release.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.



Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6fcca5cb4..e4f6029fc 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,7 +20,7 @@ jobs:
       - uses: gradle/wrapper-validation-action@v1
 
       - name: Set up JDK ${{ matrix.java }}
-        uses: actions/setup-java@v3
+        uses: actions/setup-java@v4
         with:
           java-version: ${{ matrix.java }}
           distribution: 'zulu'
diff --git a/.github/workflows/codeguru-reviewer.yml b/.github/workflows/codeguru-reviewer.yml
index 4816684ec..5a2c81847 100644
--- a/.github/workflows/codeguru-reviewer.yml
+++ b/.github/workflows/codeguru-reviewer.yml
@@ -30,7 +30,7 @@ jobs:
 
     - name: Set up JDK 1.8
       if: steps.iam-role.outcome == 'success'
-      uses: actions/setup-java@v3
+      uses: actions/setup-java@v4
       with:
         distribution: 'zulu'
         java-version: 8
diff --git a/.github/workflows/sdk-codegen-ci.yml b/.github/workflows/sdk-codegen-ci.yml
index 69a29f3d5..50cada842 100644
--- a/.github/workflows/sdk-codegen-ci.yml
+++ b/.github/workflows/sdk-codegen-ci.yml
@@ -46,7 +46,7 @@ jobs:
   build-aws-sdk-go-v2:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/setup-java@v3
+      - uses: actions/setup-java@v4
         with:
           java-version: 17
           distribution: 'zulu'
@@ -130,7 +130,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: swift-actions/setup-swift@v1
-      - uses: actions/setup-java@v3
+      - uses: actions/setup-java@v4
         with:
           java-version: 11
           distribution: 'zulu'
@@ -191,7 +191,7 @@ jobs:
         with:
           toolchain: 1.62.1
 
-      - uses: actions/setup-java@v3
+      - uses: actions/setup-java@v4
         with:
           java-version: 11
           distribution: 'zulu'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc4a89d48..c0c9c79d6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,22 @@
 # Smithy Changelog
 
+## 1.42.0 (2023-12-07)
+
+### Features
+
+* Added the `@aws.auth#sigv4a` auth trait. ([#2032](https://github.com/smithy-lang/smithy/pull/2032))
+* Added the `timestampFormat` and `httpChecksumRequired` traits to protocols. ([#2054](https://github.com/smithy-lang/smithy/pull/2054), [#2061](https://github.com/smithy-lang/smithy/pull/2061))
+
+### Bug Fixes
+
+* Fixed conversion of root `intEnum` shape to IDL 1.0 when the shape doesn't have a default value of 0. ([#2053](https://github.com/smithy-lang/smithy/pull/2053))
+* Fixed equality of `@examples` traits by overriding the `equals` method in `ExampleTrait.ErrorExample`. ([#2052](https://github.com/smithy-lang/smithy/pull/2052))
+
+### Documentation
+
+* Updated documentation for auth traits. ([#2051](https://github.com/smithy-lang/smithy/pull/2051))
+* Added documentation for smoke tests. ([#2057](https://github.com/smithy-lang/smithy/pull/2057))
+
 ## 1.41.1 (2023-11-16)
 
 ### Features
diff --git a/VERSION b/VERSION
index f86fb9cbc..a50908ca3 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-1.41.1
+1.42.0
diff --git a/build.gradle b/build.gradle
index 2e7c14a3f..f246dd12b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,7 +24,8 @@ plugins {
     id "com.github.spotbugs" version "4.7.1"
     id "io.codearte.nexus-staging" version "0.30.0"
     id "me.champeau.jmh" version "0.7.1"
-    id 'com.github.johnrengelman.shadow' version "7.1.2"
+    id "com.github.johnrengelman.shadow" version "7.1.2"
+    id "org.jreleaser" version "1.9.0"
 }
 
 ext {
@@ -39,6 +40,14 @@ allprojects {
     version = libraryVersion
 }
 
+// Only using JReleaser based on a flag allows going back to the old
+// release process if necessary. We can remove this later.
+def useJreleaser = project.hasProperty("useJreleaser")
+
+// JReleaser publishes artifacts from a local staging repository, rather than maven local.
+// https://jreleaser.org/guide/latest/examples/maven/staging-artifacts.html#_gradle
+def stagingDirectory = rootProject.layout.buildDirectory.dir("staging")
+
 subprojects {
     apply plugin: "java-library"
 
@@ -127,14 +136,23 @@ subprojects {
 
     publishing {
         repositories {
-            mavenCentral {
-                url = uri("https://aws.oss.sonatype.org/service/local/staging/deploy/maven2/")
-                if (project.hasProperty("sonatypeUser")) {
-                    credentials {
-                        username = project.property("sonatypeUser")
-                        password = project.property("sonatypePassword")
-                    }
+            // JReleaser's `publish` task publishes to all repositories, so only configure one.
+            if (useJreleaser) {
+                maven {
+                    name = "localStaging"
+                    url = stagingDirectory
                 }
+            } else {
+               mavenCentral {
+                   name = "sonatypeNexus"
+                   url = uri("https://aws.oss.sonatype.org/service/local/staging/deploy/maven2/")
+                   if (project.hasProperty("sonatypeUser")) {
+                       credentials {
+                           username = project.property("sonatypeUser")
+                           password = project.property("sonatypePassword")
+                       }
+                   }
+               }
             }
         }
 
@@ -271,6 +289,53 @@ allprojects {
     }
 }
 
+if (useJreleaser) {
+    jreleaser {
+        dryrun = false
+
+        // Used for creating a tagged release, uploading files and generating changelog.
+        // In the future we can set this up to push release tags to GitHub, but for now it's
+        // set up to do nothing.
+        // https://jreleaser.org/guide/latest/reference/release/index.html
+        release {
+            generic {
+                enabled = true
+                skipRelease = true
+            }
+        }
+
+        // Used to announce a release to configured announcers.
+        // https://jreleaser.org/guide/latest/reference/announce/index.html
+        announce {
+            active = "NEVER"
+        }
+
+        // Signing configuration.
+        // https://jreleaser.org/guide/latest/reference/signing.html
+        signing {
+            active = "ALWAYS"
+            armored = true
+        }
+
+        // Configuration for deploying to Maven Central.
+        // https://jreleaser.org/guide/latest/examples/maven/maven-central.html#_gradle
+        deploy {
+            maven {
+                nexus2 {
+                    'maven-central' {
+                        active = "ALWAYS"
+                        url = "https://aws.oss.sonatype.org/service/local"
+                        snapshotUrl = "https://aws.oss.sonatype.org/content/repositories/snapshots"
+                        closeRepository = false
+                        releaseRepository = false
+                        stagingRepository(stagingDirectory.get().toString())
+                    }
+                }
+            }
+        }
+    }
+}
+
 /*
  * Sonatype Staging Finalization
  * ====================================================
@@ -280,7 +345,7 @@ allprojects {
  * validated. This configuration is for the root project because
  * it operates at the "group" level.
  */
-if (project.hasProperty("sonatypeUser") && project.hasProperty("sonatypePassword")) {
+if (!useJreleaser && project.hasProperty("sonatypeUser") && project.hasProperty("sonatypePassword")) {
     apply plugin: "io.codearte.nexus-staging"
     nexusStaging {
         packageGroup = "software.amazon"
diff --git a/docs/source-2.0/aws/aws-auth.rst b/docs/source-2.0/aws/aws-auth.rst
index cad432300..30a7f581e 100644
--- a/docs/source-2.0/aws/aws-auth.rst
+++ b/docs/source-2.0/aws/aws-auth.rst
@@ -33,7 +33,8 @@ Trait value
           - **Required**. The signature version 4 service signing name to use
             in the `credential scope`_ when signing requests. This value MUST
             NOT be empty. This value SHOULD match the ``arnNamespace`` property
-            of the :ref:`aws.api#service-trait`.
+            of the :ref:`aws.api#service-trait` if present and the ``name``
+            property of the :ref:`aws.auth#sigv4a-trait` if present.
 
 If a request contains the ``Authorization`` header or a query string parameter
 with the name of ``X-Amz-Algorithm`` containing the value ``AWS4-HMAC-SHA256``,
@@ -59,6 +60,65 @@ unauthenticated request.
     }
 
 
+.. smithy-trait:: aws.auth#sigv4a
+.. _aws.auth#sigv4a-trait:
+
+-------------------------
+``aws.auth#sigv4a`` trait
+-------------------------
+
+Trait summary
+    The ``aws.auth#sigv4a`` trait adds support for AWS Signature Version 4
+    Asymmetric (SigV4A), an extension of `AWS signature version 4`_ (SigV4), to
+    a service.
+Trait selector
+    ``service[trait|aws.auth#sigv4]``
+Trait value
+    An ``object`` that supports the following properties:
+
+    .. list-table::
+        :header-rows: 1
+        :widths: 10 20 70
+
+        * - Property
+          - Type
+          - Description
+        * - name
+          - ``string``
+          - **Required**. The signature version 4a service signing name to use
+            in the `credential scope`_ when signing requests. This value MUST
+            NOT be empty. This value SHOULD match the ``arnNamespace`` property
+            of the :ref:`aws.api#service-trait` if present and the ``name``
+            property of the :ref:`aws.auth#sigv4-trait`.
+
+SigV4A is nearly identical to SigV4, but also uses public-private keys and
+asymmetric cryptographic signatures for every request. Most notably, SigV4A
+supports signatures for multi-region API requests.
+
+.. code-block:: smithy
+
+    $version: "2"
+
+    namespace aws.fooBaz
+
+    use aws.api#service
+    use aws.auth#sigv4
+    use aws.auth#sigv4a
+    use aws.protocols#restJson1
+
+    // This service is an AWS service that prioritizes SigV4A
+    // authentication before SigV4 authentication.
+    // Note that services that support SigV4A MUST support SigV4.
+    @service(sdkId: "Some Value")
+    @auth([sigv4a, sigv4])
+    @sigv4(name: "foobaz")
+    @sigv4a(name: "foobaz")
+    @restJson1
+    service FooBaz {
+        version: "2018-03-17"
+    }
+
+
 .. smithy-trait:: aws.auth#unsignedPayload
 .. _aws.auth#unsignedPayload-trait:
 
diff --git a/docs/source-2.0/aws/protocols/aws-restjson1-protocol.rst b/docs/source-2.0/aws/protocols/aws-restjson1-protocol.rst
index 5ebe9a71a..9ed49343d 100644
--- a/docs/source-2.0/aws/protocols/aws-restjson1-protocol.rst
+++ b/docs/source-2.0/aws/protocols/aws-restjson1-protocol.rst
@@ -174,6 +174,8 @@ that affect serialization:
       - Binds a top-level input structure member to a query string parameter.
     * - :ref:`httpQueryParams <httpQueryParams-trait>`
       - Binds a map of key-value pairs to query string parameters.
+    * - :ref:`httpChecksumRequired <httpChecksumRequired-trait>`
+      - Indicates that requests MUST send a checksum.
     * - :ref:`jsonName <jsonName-trait>`
       - By default, the JSON property names used in serialized structures are
         the same as a structure member name. The ``jsonName`` trait changes
diff --git a/docs/source-2.0/aws/protocols/aws-restxml-protocol.rst b/docs/source-2.0/aws/protocols/aws-restxml-protocol.rst
index e266fc653..2ce6eaecf 100644
--- a/docs/source-2.0/aws/protocols/aws-restxml-protocol.rst
+++ b/docs/source-2.0/aws/protocols/aws-restxml-protocol.rst
@@ -163,6 +163,8 @@ that affect serialization:
       - Binds a top-level input structure member to a query string parameter.
     * - :ref:`httpQueryParams <httpQueryParams-trait>`
       - Binds a map of key-value pairs to query string parameters.
+    * - :ref:`httpChecksumRequired <httpChecksumRequired-trait>`
+      - Indicates that requests MUST send a checksum.
     * - :ref:`xmlAttribute <xmlAttribute-trait>`
       - Serializes an object property as an XML attribute rather than a nested
         XML element.
diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/SigV4ATrait.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/SigV4ATrait.java
new file mode 100644
index 000000000..bdb702f83
--- /dev/null
+++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/SigV4ATrait.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.traits.auth;
+
+import software.amazon.smithy.model.node.Node;
+import software.amazon.smithy.model.node.ObjectNode;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.AbstractTrait;
+import software.amazon.smithy.model.traits.AbstractTraitBuilder;
+import software.amazon.smithy.model.traits.Trait;
+import software.amazon.smithy.utils.SmithyBuilder;
+import software.amazon.smithy.utils.ToSmithyBuilder;
+
+/**
+ * Adds AWS Signature Version 4 Asymmetric authentication to a service or operation.
+ */
+public final class SigV4ATrait extends AbstractTrait implements ToSmithyBuilder<SigV4ATrait> {
+    public static final ShapeId ID = ShapeId.from("aws.auth#sigv4a");
+    private static final String NAME = "name";
+
+    private final String name;
+
+    private SigV4ATrait(Builder builder) {
+        super(ID, builder.getSourceLocation());
+        this.name = SmithyBuilder.requiredState(NAME, builder.name);
+    }
+
+    /**
+     * Gets the service signing name.
+     *
+     * @return the service signing name
+     */
+    public String getName() {
+        return name;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    @Override
+    public Builder toBuilder() {
+        return builder()
+            .sourceLocation(getSourceLocation())
+            .name(getName());
+    }
+
+    @Override
+    protected Node createNode() {
+        return Node.objectNodeBuilder()
+            .sourceLocation(getSourceLocation())
+            .withMember(NAME, getName())
+            .build();
+    }
+
+    public static final class Builder extends AbstractTraitBuilder<SigV4ATrait, Builder> {
+        private String name;
+
+        private Builder() {}
+
+        @Override
+        public SigV4ATrait build() {
+            return new SigV4ATrait(this);
+        }
+
+        public Builder name(String name) {
+            this.name = name;
+            return this;
+        }
+    }
+
+    public static final class Provider extends AbstractTrait.Provider {
+        public Provider() {
+            super(ID);
+        }
+
+        @Override
+        public Trait createTrait(ShapeId target, Node value) {
+            ObjectNode objectNode = value.expectObjectNode();
+            Builder builder = builder().sourceLocation(value);
+            builder.name(objectNode.expectStringMember(NAME).getValue());
+            SigV4ATrait result = builder.build();
+            result.setNodeCache(objectNode);
+            return result;
+        }
+    }
+}
diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/SigV4TraitsValidator.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/SigV4TraitsValidator.java
new file mode 100644
index 000000000..ed64f87ec
--- /dev/null
+++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/SigV4TraitsValidator.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.traits.auth;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import software.amazon.smithy.aws.traits.ServiceTrait;
+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.validation.AbstractValidator;
+import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.utils.SmithyInternalApi;
+
+/**
+ * Validates AWS Service, SigV4, and SigV4A traits.
+ */
+@SmithyInternalApi
+public final class SigV4TraitsValidator extends AbstractValidator {
+    private static final ShapeId SERVICE_ARN_NAMESPACE = ServiceTrait.ID.withMember("arnNamespace");
+    private static final ShapeId SIGV4_NAME = SigV4Trait.ID.withMember("name");
+    private static final ShapeId SIGV4A_NAME = SigV4ATrait.ID.withMember("name");
+
+    @Override
+    public List<ValidationEvent> validate(Model model) {
+        List<ValidationEvent> events = new ArrayList<>();
+        for (ServiceShape service : model.getServiceShapes()) {
+            events.addAll(validateService(model, service));
+        }
+        return events;
+    }
+
+    /**
+     * Validates Service and SigV4 traits.
+     *
+     * - service$arnNamespace, sigv4$name, and sigv4a$name SHOULD be equal. Otherwise, emits warnings.
+     */
+    private List<ValidationEvent> validateService(Model model, ServiceShape service) {
+        List<ValidationEvent> events = new ArrayList<>();
+        Optional<ServiceTrait> serviceTraitOptional = service.getTrait(ServiceTrait.class);
+        Optional<SigV4Trait> sigv4TraitOptional = service.getTrait(SigV4Trait.class);
+        Optional<SigV4ATrait> sigv4aTraitOptional = service.getTrait(SigV4ATrait.class);
+        if (serviceTraitOptional.isPresent()) {
+            String serviceArnNamespace = serviceTraitOptional.get().getArnNamespace();
+            // Check service$arnNamespace with sigv4$name
+            if (sigv4TraitOptional.isPresent()) {
+                String sigv4Name = sigv4TraitOptional.get().getName();
+                if (!serviceArnNamespace.equals(sigv4Name)) {
+                    events.add(createValuesShouldMatchWarning(
+                        service,
+                        SERVICE_ARN_NAMESPACE, serviceArnNamespace,
+                        SIGV4_NAME, sigv4Name));
+                }
+            }
+            // Check service$arnNamespace with sigv4a$name
+            if (sigv4aTraitOptional.isPresent()) {
+                String sigv4aName = sigv4aTraitOptional.get().getName();
+                if (!serviceArnNamespace.equals(sigv4aName)) {
+                    events.add(createValuesShouldMatchWarning(
+                        service,
+                        SERVICE_ARN_NAMESPACE, serviceArnNamespace,
+                        SIGV4A_NAME, sigv4aName));
+                }
+            }
+        }
+        // Check sigv4$name with sigv4a$name
+        if (sigv4TraitOptional.isPresent() && sigv4aTraitOptional.isPresent()) {
+            String sigv4Name = sigv4TraitOptional.get().getName();
+            String sigv4aName = sigv4aTraitOptional.get().getName();
+            if (!sigv4Name.equals(sigv4aName)) {
+                events.add(createValuesShouldMatchWarning(
+                    service,
+                    SIGV4_NAME, sigv4Name,
+                    SIGV4A_NAME, sigv4aName));
+            }
+        }
+        return events;
+    }
+
+    private ValidationEvent createValuesShouldMatchWarning(
+        ServiceShape service,
+        ShapeId member1,
+        String value1,
+        ShapeId member2,
+        String value2
+    ) {
+        return warning(service, String.format(
+            "Value for `%s` \"%s\" and value for `%s` \"%s\" SHOULD match.",
+            member1.toString(), value1, member2.toString(), value2));
+    }
+}
diff --git a/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService b/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService
index 77e89519f..6f5b0ca5c 100644
--- a/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService
+++ b/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService
@@ -21,5 +21,6 @@ software.amazon.smithy.aws.traits.protocols.RestXmlTrait$Provider
 software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait$Provider
 software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait$Provider
 software.amazon.smithy.aws.traits.auth.SigV4Trait$Provider
+software.amazon.smithy.aws.traits.auth.SigV4ATrait$Provider
 software.amazon.smithy.aws.traits.tagging.TagEnabledTrait$Provider
 software.amazon.smithy.aws.traits.tagging.TaggableTrait$Provider
diff --git a/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
index eba9e65bd..80a840407 100644
--- a/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
+++ b/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
@@ -11,3 +11,4 @@ software.amazon.smithy.aws.traits.tagging.TaggableResourceValidator
 software.amazon.smithy.aws.traits.tagging.TagResourcePropertyTypeValidator
 software.amazon.smithy.aws.traits.tagging.TagResourcePropertyNameValidator
 software.amazon.smithy.aws.traits.ErrorRenameValidator
+software.amazon.smithy.aws.traits.auth.SigV4TraitsValidator
diff --git a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.auth.smithy b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.auth.smithy
index 7c67bb404..155960783 100644
--- a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.auth.smithy
+++ b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.auth.smithy
@@ -19,18 +19,40 @@ structure cognitoUserPools {
 /// security, most requests to AWS must be signed with an access key, which consists
 /// of an access key ID and secret access key. These two keys are commonly referred to
 /// as your security credentials.
-@authDefinition(traits: [unsignedPayload])
-@externalDocumentation(
-    Reference: "https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html"
+@authDefinition(
+    traits: [unsignedPayload]
 )
+@externalDocumentation(Reference: "https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html")
 @trait(selector: "service")
 structure sigv4 {
     /// The signature version 4 service signing name to use in the credential
     /// scope when signing requests. This value SHOULD match the `arnNamespace`
-    /// property of the `aws.api#service-trait`.
-    @externalDocumentation(
-        Reference: "https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html"
-    )
+    /// property of the `aws.api#service` trait if present and the `name`
+    /// property of the `aws.api#sigv4a` trait if present.
+    @externalDocumentation(Reference: "https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html")
+    @length(min: 1)
+    @required
+    name: String
+}
+
+/// Signature Version 4 Asymmetric (SigV4A), an extension of Signature Version 4 (SigV4), is the
+/// process to add authentication information to AWS requests sent by HTTP. SigV4A is nearly
+/// identical to SigV4, but also uses public-private keys and asymmetric cryptographic signatures
+/// for every request. Most notably, SigV4A supports signatures for multi-region API requests.
+@authDefinition(
+    traits: [unsignedPayload]
+)
+@externalDocumentation(
+    Reference: "https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html"
+    Examples: "https://github.com/aws-samples/sigv4a-signing-examples"
+)
+@trait(selector: "service[trait|aws.auth#sigv4]")
+structure sigv4a {
+    /// The signature version 4a service signing name to use in the credential
+    /// scope when signing requests. This value SHOULD match the `arnNamespace`
+    /// property of the `aws.api#service` trait if present and the `name`
+    /// property of the `aws.api#sigv4` trait.
+    @externalDocumentation(Reference: "https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html")
     @length(min: 1)
     @required
     name: String
diff --git a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.smithy b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.smithy
index 5b9746e23..35d413ff0 100644
--- a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.smithy
+++ b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.protocols.smithy
@@ -148,6 +148,7 @@ structure httpChecksum {
         httpQuery
         httpQueryParams
         httpResponseCode
+        httpChecksumRequired
         jsonName
     ]
 )
@@ -172,6 +173,7 @@ structure restJson1 with [HttpConfiguration] {}
         httpQuery
         httpQueryParams
         httpResponseCode
+        httpChecksumRequired
         xmlAttribute
         xmlFlattened
         xmlName
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/OperationIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/OperationIndex.java
index 531c20765..0b0fc2acc 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/OperationIndex.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/OperationIndex.java
@@ -18,6 +18,7 @@ package software.amazon.smithy.model.knowledge;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -37,6 +38,7 @@ import software.amazon.smithy.model.traits.InputTrait;
 import software.amazon.smithy.model.traits.OutputTrait;
 import software.amazon.smithy.model.traits.UnitTypeTrait;
 import software.amazon.smithy.utils.ListUtils;
+import software.amazon.smithy.utils.SetUtils;
 
 /**
  * Index of operation IDs to their resolved input, output, and error
@@ -50,11 +52,20 @@ public final class OperationIndex implements KnowledgeIndex {
     private final Map<ShapeId, StructureShape> inputs = new HashMap<>();
     private final Map<ShapeId, StructureShape> outputs = new HashMap<>();
     private final Map<ShapeId, List<StructureShape>> errors = new HashMap<>();
+    private final Map<ShapeId, Set<OperationShape>> boundInputOperations = new HashMap<>();
+    private final Map<ShapeId, Set<OperationShape>> boundOutputOperations = new HashMap<>();
+    private final Map<ShapeId, Set<Shape>> boundErrorShapes = new HashMap<>();
 
     public OperationIndex(Model model) {
         for (OperationShape operation : model.getOperationShapes()) {
-            getStructure(model, operation.getInputShape()).ifPresent(shape -> inputs.put(operation.getId(), shape));
-            getStructure(model, operation.getOutputShape()).ifPresent(shape -> outputs.put(operation.getId(), shape));
+            getStructure(model, operation.getInputShape()).ifPresent(shape -> {
+                inputs.put(operation.getId(), shape);
+                boundInputOperations.computeIfAbsent(shape.getId(), id -> new HashSet<>()).add(operation);
+            });
+            getStructure(model, operation.getOutputShape()).ifPresent(shape -> {
+                outputs.put(operation.getId(), shape);
+                boundOutputOperations.computeIfAbsent(shape.getId(), id -> new HashSet<>()).add(operation);
+            });
             addErrorsFromShape(model, operation.getId(), operation.getErrors());
         }
 
@@ -65,8 +76,10 @@ public final class OperationIndex implements KnowledgeIndex {
 
     private void addErrorsFromShape(Model model, ShapeId source, List<ShapeId> errorShapeIds) {
         List<StructureShape> errorShapes = new ArrayList<>(errorShapeIds.size());
+        Shape sourceShape = model.expectShape(source);
         for (ShapeId target : errorShapeIds) {
             model.getShape(target).flatMap(Shape::asStructureShape).ifPresent(errorShapes::add);
+            boundErrorShapes.computeIfAbsent(target, id -> new HashSet<>()).add(sourceShape);
         }
         errors.put(source, errorShapes);
     }
@@ -158,6 +171,16 @@ public final class OperationIndex implements KnowledgeIndex {
         return false;
     }
 
+    /**
+     * Gets all the operations that bind the given shape as input.
+     *
+     * @param input The structure that may be used as input.
+     * @return Returns a set of operations that bind the given input shape.
+     */
+    public Set<OperationShape> getInputBindings(ToShapeId input) {
+        return SetUtils.copyOf(boundInputOperations.getOrDefault(input.toShapeId(), Collections.emptySet()));
+    }
+
     /**
      * Gets the optional output structure of an operation, and returns an
      * empty optional if the output targets {@code smithy.api#Unit}.
@@ -241,6 +264,16 @@ public final class OperationIndex implements KnowledgeIndex {
         return false;
     }
 
+    /**
+     * Gets all the operations that bind the given shape as output.
+     *
+     * @param output The structure that may be used as output.
+     * @return Returns a set of operations that bind the given output shape.
+     */
+    public Set<OperationShape> getOutputBindings(ToShapeId output) {
+        return SetUtils.copyOf(boundOutputOperations.getOrDefault(output.toShapeId(), Collections.emptySet()));
+    }
+
     /**
      * Gets the list of error structures defined on an operation.
      *
@@ -275,4 +308,14 @@ public final class OperationIndex implements KnowledgeIndex {
     private Optional<StructureShape> getStructure(Model model, ToShapeId id) {
         return model.getShape(id.toShapeId()).flatMap(Shape::asStructureShape);
     }
+
+    /**
+     * Gets all the operations and services that bind the given shape as an error.
+     *
+     * @param error The structure that may be used as an error.
+     * @return Returns a set of operations and services that bind the given error shape.
+     */
+    public Set<Shape> getErrorBindings(ToShapeId error) {
+        return SetUtils.copyOf(boundErrorShapes.getOrDefault(error.toShapeId(), Collections.emptySet()));
+    }
 }
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/ExamplesTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/ExamplesTrait.java
index 5dbac8017..486c51fcd 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/traits/ExamplesTrait.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/ExamplesTrait.java
@@ -337,6 +337,23 @@ public final class ExamplesTrait extends AbstractTrait implements ToSmithyBuilde
                 return this;
             }
         }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(shapeId, content);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == this) {
+                return true;
+            }
+            if (other == null || other.getClass() != this.getClass()) {
+                return false;
+            }
+            ErrorExample otherExample = (ErrorExample) other;
+            return Objects.equals(shapeId, otherExample.shapeId) && Objects.equals(content, otherExample.content);
+        }
     }
 
     public static final class Provider implements TraitService {


Test patch:
diff --git a/smithy-aws-protocol-tests/model/restXml/document-lists.smithy b/smithy-aws-protocol-tests/model/restXml/document-lists.smithy
index e5fb3fae1..b2dc28b99 100644
--- a/smithy-aws-protocol-tests/model/restXml/document-lists.smithy
+++ b/smithy-aws-protocol-tests/model/restXml/document-lists.smithy
@@ -1,6 +1,8 @@
 // This file defines test cases that serialize lists in XML documents.
 
 $version: "2.0"
+$operationInputSuffix: "Request"
+$operationOutputSuffix: "Response"
 
 namespace aws.protocoltests.restxml
 
@@ -33,8 +35,8 @@ use smithy.test#httpResponseTests
 @idempotent
 @http(uri: "/XmlLists", method: "PUT")
 operation XmlLists {
-    input: XmlListsInputOutput,
-    output: XmlListsInputOutput,
+    input := with [XmlListsInputOutput] {}
+    output := with [XmlListsInputOutput] {}
 }
 
 apply XmlLists @httpRequestTests([
@@ -45,7 +47,7 @@ apply XmlLists @httpRequestTests([
         method: "PUT",
         uri: "/XmlLists",
         body: """
-              <XmlListsInputOutput>
+              <XmlListsRequest>
                   <stringList>
                       <member>foo</member>
                       <member>bar</member>
@@ -110,7 +112,7 @@ apply XmlLists @httpRequestTests([
                       <value>7</value>
                       <other>8</other>
                   </flattenedStructureList>
-              </XmlListsInputOutput>
+              </XmlListsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -159,7 +161,7 @@ apply XmlLists @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlListsInputOutput>
+              <XmlListsResponse>
                   <stringList>
                       <member>foo</member>
                       <member>bar</member>
@@ -228,7 +230,7 @@ apply XmlLists @httpResponseTests([
                       <value>7</value>
                       <other>8</other>
                   </flattenedStructureList>
-              </XmlListsInputOutput>
+              </XmlListsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -276,8 +278,8 @@ apply XmlLists @httpResponseTests([
 @http(uri: "/XmlEmptyLists", method: "PUT")
 @tags(["client-only"])
 operation XmlEmptyLists {
-    input: XmlListsInputOutput,
-    output: XmlListsInputOutput,
+    input := with [XmlListsInputOutput] {}
+    output := with [XmlListsInputOutput] {}
 }
 
 apply XmlEmptyLists @httpRequestTests([
@@ -288,10 +290,10 @@ apply XmlEmptyLists @httpRequestTests([
         method: "PUT",
         uri: "/XmlEmptyLists",
         body: """
-              <XmlListsInputOutput>
+              <XmlEmptyListsRequest>
                       <stringList></stringList>
                       <stringSet></stringSet>
-              </XmlListsInputOutput>
+              </XmlEmptyListsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -312,10 +314,10 @@ apply XmlEmptyLists @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlListsInputOutput>
+              <XmlEmptyListsResponse>
                       <stringList/>
                       <stringSet></stringSet>
-              </XmlListsInputOutput>
+              </XmlEmptyListsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -329,6 +331,7 @@ apply XmlEmptyLists @httpResponseTests([
     }
 ])
 
+@mixin
 structure XmlListsInputOutput {
     stringList: StringList,
 
diff --git a/smithy-aws-protocol-tests/model/restXml/document-maps.smithy b/smithy-aws-protocol-tests/model/restXml/document-maps.smithy
index 9b479336f..253c97aed 100644
--- a/smithy-aws-protocol-tests/model/restXml/document-maps.smithy
+++ b/smithy-aws-protocol-tests/model/restXml/document-maps.smithy
@@ -1,6 +1,8 @@
 // This file defines test cases that serialize maps in XML payloads.
 
 $version: "2.0"
+$operationInputSuffix: "Request"
+$operationOutputSuffix: "Response"
 
 namespace aws.protocoltests.restxml
 
@@ -13,8 +15,12 @@ use smithy.test#httpResponseTests
 /// The example tests basic map serialization.
 @http(uri: "/XmlMaps", method: "POST")
 operation XmlMaps {
-    input: XmlMapsInputOutput,
-    output: XmlMapsInputOutput
+    input := {
+        myMap: XmlMapsInputOutputMap
+    }
+    output := {
+        myMap: XmlMapsInputOutputMap
+    }
 }
 
 apply XmlMaps @httpRequestTests([
@@ -25,7 +31,7 @@ apply XmlMaps @httpRequestTests([
         method: "POST",
         uri: "/XmlMaps",
         body: """
-              <XmlMapsInputOutput>
+              <XmlMapsRequest>
                   <myMap>
                       <entry>
                           <key>foo</key>
@@ -40,7 +46,7 @@ apply XmlMaps @httpRequestTests([
                           </value>
                       </entry>
                   </myMap>
-              </XmlMapsInputOutput>
+              </XmlMapsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -66,7 +72,7 @@ apply XmlMaps @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlMapsInputOutput>
+              <XmlMapsRequest>
                   <myMap>
                       <entry>
                           <key>foo</key>
@@ -81,7 +87,7 @@ apply XmlMaps @httpResponseTests([
                           </value>
                       </entry>
                   </myMap>
-              </XmlMapsInputOutput>
+              </XmlMapsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -103,8 +109,12 @@ apply XmlMaps @httpResponseTests([
 @http(uri: "/XmlEmptyMaps", method: "POST")
 @tags(["client-only"])
 operation XmlEmptyMaps {
-    input: XmlMapsInputOutput,
-    output: XmlMapsInputOutput
+    input := {
+        myMap: XmlMapsInputOutputMap
+    }
+    output := {
+        myMap: XmlMapsInputOutputMap
+    }
 }
 
 apply XmlEmptyMaps @httpRequestTests([
@@ -115,9 +125,9 @@ apply XmlEmptyMaps @httpRequestTests([
         method: "POST",
         uri: "/XmlEmptyMaps",
         body: """
-              <XmlMapsInputOutput>
+              <XmlEmptyMapsRequest>
                   <myMap></myMap>
-              </XmlMapsInputOutput>
+              </XmlEmptyMapsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -137,9 +147,9 @@ apply XmlEmptyMaps @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlMapsInputOutput>
+              <XmlEmptyMapsResponse>
                   <myMap></myMap>
-              </XmlMapsInputOutput>
+              </XmlEmptyMapsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -156,9 +166,9 @@ apply XmlEmptyMaps @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlMapsInputOutput>
+              <XmlEmptyMapsResponse>
                   <myMap/>
-              </XmlMapsInputOutput>
+              </XmlEmptyMapsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -171,10 +181,6 @@ apply XmlEmptyMaps @httpResponseTests([
     }
 ])
 
-structure XmlMapsInputOutput {
-    myMap: XmlMapsInputOutputMap,
-}
-
 map XmlMapsInputOutputMap {
     key: String,
     value: GreetingStruct
@@ -183,8 +189,12 @@ map XmlMapsInputOutputMap {
 // This example tests maps with @xmlName on members.
 @http(uri: "/XmlMapsXmlName", method: "POST")
 operation XmlMapsXmlName {
-    input: XmlMapsXmlNameInputOutput,
-    output: XmlMapsXmlNameInputOutput
+    input := {
+        myMap: XmlMapsXmlNameInputOutputMap
+    }
+    output := {
+        myMap: XmlMapsXmlNameInputOutputMap
+    }
 }
 
 apply XmlMapsXmlName @httpRequestTests([
@@ -195,7 +205,7 @@ apply XmlMapsXmlName @httpRequestTests([
         method: "POST",
         uri: "/XmlMapsXmlName",
         body: """
-              <XmlMapsXmlNameInputOutput>
+              <XmlMapsXmlNameRequest>
                   <myMap>
                       <entry>
                           <Attribute>foo</Attribute>
@@ -210,7 +220,7 @@ apply XmlMapsXmlName @httpRequestTests([
                           </Setting>
                       </entry>
                   </myMap>
-              </XmlMapsXmlNameInputOutput>
+              </XmlMapsXmlNameRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -236,7 +246,7 @@ apply XmlMapsXmlName @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlMapsXmlNameInputOutput>
+              <XmlMapsXmlNameResponse>
                   <myMap>
                       <entry>
                           <Attribute>foo</Attribute>
@@ -251,7 +261,7 @@ apply XmlMapsXmlName @httpResponseTests([
                           </Setting>
                       </entry>
                   </myMap>
-              </XmlMapsXmlNameInputOutput>
+              </XmlMapsXmlNameResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -270,10 +280,6 @@ apply XmlMapsXmlName @httpResponseTests([
     }
 ])
 
-structure XmlMapsXmlNameInputOutput {
-    myMap: XmlMapsXmlNameInputOutputMap,
-}
-
 map XmlMapsXmlNameInputOutputMap {
     @xmlName("Attribute")
     key: String,
@@ -285,8 +291,14 @@ map XmlMapsXmlNameInputOutputMap {
 /// Flattened maps
 @http(uri: "/FlattenedXmlMap", method: "POST")
 operation FlattenedXmlMap {
-    input: FlattenedXmlMapInputOutput,
-    output: FlattenedXmlMapInputOutput
+    input := {
+        @xmlFlattened
+        myMap: FooEnumMap
+    }
+    output := {
+        @xmlFlattened
+        myMap: FooEnumMap
+    }
 }
 
 apply FlattenedXmlMap @httpRequestTests([
@@ -297,7 +309,7 @@ apply FlattenedXmlMap @httpRequestTests([
         method: "POST",
         uri: "/FlattenedXmlMap",
         body: """
-              <FlattenedXmlMapInputOutput>
+              <FlattenedXmlMapRequest>
                   <myMap>
                       <key>foo</key>
                       <value>Foo</value>
@@ -306,7 +318,7 @@ apply FlattenedXmlMap @httpRequestTests([
                       <key>baz</key>
                       <value>Baz</value>
                   </myMap>
-              </FlattenedXmlMapInputOutput>""",
+              </FlattenedXmlMapRequest>""",
         bodyMediaType: "application/xml",
         headers: {
             "Content-Type": "application/xml"
@@ -327,7 +339,7 @@ apply FlattenedXmlMap @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <FlattenedXmlMapInputOutput>
+              <FlattenedXmlMapResponse>
                   <myMap>
                       <key>foo</key>
                       <value>Foo</value>
@@ -336,7 +348,7 @@ apply FlattenedXmlMap @httpResponseTests([
                       <key>baz</key>
                       <value>Baz</value>
                   </myMap>
-              </FlattenedXmlMapInputOutput>""",
+              </FlattenedXmlMapResponse>""",
         bodyMediaType: "application/xml",
         headers: {
             "Content-Type": "application/xml"
@@ -350,16 +362,19 @@ apply FlattenedXmlMap @httpResponseTests([
     }
 ])
 
-structure FlattenedXmlMapInputOutput {
-    @xmlFlattened
-    myMap: FooEnumMap,
-}
-
 /// Flattened maps with @xmlName
 @http(uri: "/FlattenedXmlMapWithXmlName", method: "POST")
 operation FlattenedXmlMapWithXmlName {
-    input: FlattenedXmlMapWithXmlNameInputOutput,
-    output: FlattenedXmlMapWithXmlNameInputOutput
+    input := {
+        @xmlFlattened
+        @xmlName("KVP")
+        myMap: FlattenedXmlMapWithXmlNameInputOutputMap,
+    }
+    output := {
+        @xmlFlattened
+        @xmlName("KVP")
+        myMap: FlattenedXmlMapWithXmlNameInputOutputMap
+    }
 }
 
 apply FlattenedXmlMapWithXmlName @httpRequestTests([
@@ -370,7 +385,7 @@ apply FlattenedXmlMapWithXmlName @httpRequestTests([
         method: "POST",
         uri: "/FlattenedXmlMapWithXmlName",
         body: """
-              <FlattenedXmlMapWithXmlNameInputOutput>
+              <FlattenedXmlMapWithXmlNameRequest>
                   <KVP>
                       <K>a</K>
                       <V>A</V>
@@ -379,7 +394,7 @@ apply FlattenedXmlMapWithXmlName @httpRequestTests([
                       <K>b</K>
                       <V>B</V>
                   </KVP>
-              </FlattenedXmlMapWithXmlNameInputOutput>""",
+              </FlattenedXmlMapWithXmlNameRequest>""",
         bodyMediaType: "application/xml",
         headers: {
             "Content-Type": "application/xml"
@@ -400,7 +415,7 @@ apply FlattenedXmlMapWithXmlName @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <FlattenedXmlMapWithXmlNameInputOutput>
+              <FlattenedXmlMapWithXmlNameResponse>
                   <KVP>
                       <K>a</K>
                       <V>A</V>
@@ -409,7 +424,7 @@ apply FlattenedXmlMapWithXmlName @httpResponseTests([
                       <K>b</K>
                       <V>B</V>
                   </KVP>
-              </FlattenedXmlMapWithXmlNameInputOutput>""",
+              </FlattenedXmlMapWithXmlNameResponse>""",
         bodyMediaType: "application/xml",
         headers: {
             "Content-Type": "application/xml"
@@ -423,12 +438,6 @@ apply FlattenedXmlMapWithXmlName @httpResponseTests([
     }
 ])
 
-structure FlattenedXmlMapWithXmlNameInputOutput {
-    @xmlFlattened
-    @xmlName("KVP")
-    myMap: FlattenedXmlMapWithXmlNameInputOutputMap,
-}
-
 map FlattenedXmlMapWithXmlNameInputOutputMap {
     @xmlName("K")
     key: String,
@@ -492,10 +501,11 @@ map FlattenedXmlMapWithXmlNamespaceOutputMap {
 
 @http(uri: "/NestedXmlMaps", method: "POST")
 operation NestedXmlMaps {
-    input: NestedXmlMapsInputOutput,
-    output: NestedXmlMapsInputOutput,
+    input := with [NestedXmlMapsInputOutput] {}
+    output := with [NestedXmlMapsInputOutput] {}
 }
 
+@mixin
 structure NestedXmlMapsInputOutput {
     nestedMap: NestedMap,
 
@@ -516,7 +526,7 @@ apply NestedXmlMaps @httpRequestTests([
         method: "POST",
         uri: "/NestedXmlMaps",
         body: """
-            <NestedXmlMapsInputOutput>
+            <NestedXmlMapsRequest>
                 <nestedMap>
                     <entry>
                         <key>foo</key>
@@ -528,7 +538,7 @@ apply NestedXmlMaps @httpRequestTests([
                         </value>
                     </entry>
                 </nestedMap>
-            </NestedXmlMapsInputOutput>""",
+            </NestedXmlMapsRequest>""",
         bodyMediaType: "application/xml",
         headers: {
             "Content-Type": "application/xml",
@@ -550,7 +560,7 @@ apply NestedXmlMaps @httpRequestTests([
         method: "POST",
         uri: "/NestedXmlMaps",
         body: """
-            <NestedXmlMapsInputOutput>
+            <NestedXmlMapsRequest>
                 <flatNestedMap>
                     <key>foo</key>
                     <value>
@@ -560,7 +570,7 @@ apply NestedXmlMaps @httpRequestTests([
                         </entry>
                     </value>
                 </flatNestedMap>
-            </NestedXmlMapsInputOutput>""",
+            </NestedXmlMapsRequest>""",
         bodyMediaType: "application/xml",
         headers: {
             "Content-Type": "application/xml",
@@ -582,7 +592,7 @@ apply NestedXmlMaps @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-            <NestedXmlMapsInputOutput>
+            <NestedXmlMapsResponse>
                 <nestedMap>
                     <entry>
                         <key>foo</key>
@@ -594,7 +604,7 @@ apply NestedXmlMaps @httpResponseTests([
                         </value>
                     </entry>
                 </nestedMap>
-            </NestedXmlMapsInputOutput>""",
+            </NestedXmlMapsResponse>""",
         bodyMediaType: "application/xml",
         headers: {
             "Content-Type": "application/xml",
@@ -615,7 +625,7 @@ apply NestedXmlMaps @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-            <NestedXmlMapsInputOutput>
+            <NestedXmlMapsResponse>
                 <flatNestedMap>
                     <key>foo</key>
                     <value>
@@ -625,7 +635,7 @@ apply NestedXmlMaps @httpResponseTests([
                         </entry>
                     </value>
                 </flatNestedMap>
-            </NestedXmlMapsInputOutput>""",
+            </NestedXmlMapsResponse>""",
         bodyMediaType: "application/xml",
         headers: {
             "Content-Type": "application/xml",
@@ -643,8 +653,8 @@ apply NestedXmlMaps @httpResponseTests([
 /// Maps with @xmlNamespace and @xmlName
 @http(uri: "/XmlMapWithXmlNamespace", method: "POST")
 operation XmlMapWithXmlNamespace {
-    input: XmlMapWithXmlNamespaceInputOutput
-    output: XmlMapWithXmlNamespaceInputOutput
+    input := with [XmlMapWithXmlNamespaceInputOutput] {}
+    output := with [XmlMapWithXmlNamespaceInputOutput] {}
 }
 
 apply XmlMapWithXmlNamespace @httpRequestTests([
@@ -655,7 +665,7 @@ apply XmlMapWithXmlNamespace @httpRequestTests([
         method: "POST",
         uri: "/XmlMapWithXmlNamespace",
         body: """
-              <XmlMapWithXmlNamespaceInputOutput>
+              <XmlMapWithXmlNamespaceRequest>
                   <KVP xmlns="https://the-member.example.com">
                       <entry>
                           <K xmlns="https://the-key.example.com">a</K>
@@ -666,7 +676,7 @@ apply XmlMapWithXmlNamespace @httpRequestTests([
                           <V xmlns="https://the-value.example.com">B</V>
                       </entry>
                   </KVP>
-              </XmlMapWithXmlNamespaceInputOutput>""",
+              </XmlMapWithXmlNamespaceRequest>""",
         bodyMediaType: "application/xml",
         headers: {
             "Content-Type": "application/xml"
@@ -687,7 +697,7 @@ apply XmlMapWithXmlNamespace @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlMapWithXmlNamespaceInputOutput>
+              <XmlMapWithXmlNamespaceResponse>
                   <KVP xmlns="https://the-member.example.com">
                       <entry>
                           <K xmlns="https://the-key.example.com">a</K>
@@ -698,7 +708,7 @@ apply XmlMapWithXmlNamespace @httpResponseTests([
                           <V xmlns="https://the-value.example.com">B</V>
                       </entry>
                   </KVP>
-              </XmlMapWithXmlNamespaceInputOutput>""",
+              </XmlMapWithXmlNamespaceResponse>""",
         bodyMediaType: "application/xml",
         headers: {
             "Content-Type": "application/xml"
@@ -712,6 +722,7 @@ apply XmlMapWithXmlNamespace @httpResponseTests([
     }
 ])
 
+@mixin
 structure XmlMapWithXmlNamespaceInputOutput {
     @xmlName("KVP")
     @xmlNamespace(uri: "https://the-member.example.com")
diff --git a/smithy-aws-protocol-tests/model/restXml/document-structs.smithy b/smithy-aws-protocol-tests/model/restXml/document-structs.smithy
index e5697aec3..8fa9e94ee 100644
--- a/smithy-aws-protocol-tests/model/restXml/document-structs.smithy
+++ b/smithy-aws-protocol-tests/model/restXml/document-structs.smithy
@@ -2,6 +2,8 @@
 // in the payload of HTTP requests and responses.
 
 $version: "2.0"
+$operationInputSuffix: "Request"
+$operationOutputSuffix: "Response"
 
 namespace aws.protocoltests.restxml
 
@@ -25,8 +27,8 @@ use smithy.test#httpResponseTests
 @idempotent
 @http(uri: "/SimpleScalarProperties", method: "PUT")
 operation SimpleScalarProperties {
-    input: SimpleScalarPropertiesInputOutput,
-    output: SimpleScalarPropertiesInputOutput
+    input := with [SimpleScalarPropertiesInputOutput] {}
+    output := with [SimpleScalarPropertiesInputOutput] {}
 }
 
 apply SimpleScalarProperties @httpRequestTests([
@@ -37,7 +39,7 @@ apply SimpleScalarProperties @httpRequestTests([
         method: "PUT",
         uri: "/SimpleScalarProperties",
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesRequest>
                   <stringValue>string</stringValue>
                   <trueBooleanValue>true</trueBooleanValue>
                   <falseBooleanValue>false</falseBooleanValue>
@@ -47,7 +49,7 @@ apply SimpleScalarProperties @httpRequestTests([
                   <longValue>4</longValue>
                   <floatValue>5.5</floatValue>
                   <DoubleDribble>6.5</DoubleDribble>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -74,9 +76,9 @@ apply SimpleScalarProperties @httpRequestTests([
         method: "PUT",
         uri: "/SimpleScalarProperties",
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesRequest>
                   <stringValue>&lt;string&gt;</stringValue>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -95,9 +97,9 @@ apply SimpleScalarProperties @httpRequestTests([
         method: "PUT",
         uri: "/SimpleScalarProperties",
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesRequest>
                   <stringValue>  string with white    space  </stringValue>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -116,9 +118,9 @@ apply SimpleScalarProperties @httpRequestTests([
         method: "PUT",
         uri: "/SimpleScalarProperties",
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesRequest>
                   <stringValue>   </stringValue>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -137,10 +139,10 @@ apply SimpleScalarProperties @httpRequestTests([
         method: "PUT",
         uri: "/SimpleScalarProperties",
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesRequest>
                   <floatValue>NaN</floatValue>
                   <DoubleDribble>NaN</DoubleDribble>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -158,10 +160,10 @@ apply SimpleScalarProperties @httpRequestTests([
         method: "PUT",
         uri: "/SimpleScalarProperties",
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesRequest>
                   <floatValue>Infinity</floatValue>
                   <DoubleDribble>Infinity</DoubleDribble>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -179,10 +181,10 @@ apply SimpleScalarProperties @httpRequestTests([
         method: "PUT",
         uri: "/SimpleScalarProperties",
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesRequest>
                   <floatValue>-Infinity</floatValue>
                   <DoubleDribble>-Infinity</DoubleDribble>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -202,7 +204,7 @@ apply SimpleScalarProperties @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesResponse>
                   <stringValue>string</stringValue>
                   <trueBooleanValue>true</trueBooleanValue>
                   <falseBooleanValue>false</falseBooleanValue>
@@ -212,7 +214,7 @@ apply SimpleScalarProperties @httpResponseTests([
                   <longValue>4</longValue>
                   <floatValue>5.5</floatValue>
                   <DoubleDribble>6.5</DoubleDribble>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -245,9 +247,9 @@ apply SimpleScalarProperties @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesResponse>
                   <stringValue>escaped data: &amp;lt;&#xD;&#10;</stringValue>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -266,9 +268,9 @@ apply SimpleScalarProperties @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesResponse>
                   <stringValue>&lt;string&gt;</stringValue>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -287,11 +289,11 @@ apply SimpleScalarProperties @httpResponseTests([
         code: 200,
         body: """
               <?xml version = "1.0" encoding = "UTF-8"?>
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesResponse>
                   <![CDATA[characters representing CDATA]]>
                   <stringValue>string</stringValue>
                   <!--xml comment-->
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -310,9 +312,9 @@ apply SimpleScalarProperties @httpResponseTests([
         code: 200,
         body: """
               <?xml version = "1.0" encoding = "UTF-8"?>
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesResponse>
                   <stringValue> string with white    space </stringValue>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -331,9 +333,9 @@ apply SimpleScalarProperties @httpResponseTests([
         code: 200,
         body: """
               <?xml version = "1.0" encoding = "UTF-8"?>
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesResponse>
                   <stringValue>  </stringValue>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -351,10 +353,10 @@ apply SimpleScalarProperties @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesResponse>
                   <floatValue>NaN</floatValue>
                   <DoubleDribble>NaN</DoubleDribble>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -371,10 +373,10 @@ apply SimpleScalarProperties @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesResponse>
                   <floatValue>Infinity</floatValue>
                   <DoubleDribble>Infinity</DoubleDribble>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -391,10 +393,10 @@ apply SimpleScalarProperties @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <SimpleScalarPropertiesInputOutput>
+              <SimpleScalarPropertiesResponse>
                   <floatValue>-Infinity</floatValue>
                   <DoubleDribble>-Infinity</DoubleDribble>
-              </SimpleScalarPropertiesInputOutput>
+              </SimpleScalarPropertiesResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -407,6 +409,7 @@ apply SimpleScalarProperties @httpResponseTests([
     },
 ])
 
+@mixin
 structure SimpleScalarPropertiesInputOutput {
     @httpHeader("X-Foo")
     foo: String,
@@ -429,8 +432,12 @@ structure SimpleScalarPropertiesInputOutput {
 @http(uri: "/XmlEmptyStrings", method: "PUT")
 @tags(["client-only"])
 operation XmlEmptyStrings {
-    input: XmlEmptyStringsInputOutput,
-    output: XmlEmptyStringsInputOutput
+    input := {
+        emptyString: String
+    }
+    output := {
+        emptyString: String
+    }
 }
 
 apply XmlEmptyStrings @httpRequestTests([
@@ -441,9 +448,9 @@ apply XmlEmptyStrings @httpRequestTests([
            method: "PUT",
            uri: "/XmlEmptyStrings",
            body: """
-                 <XmlEmptyStringsInputOutput>
+                 <XmlEmptyStringsRequest>
                      <emptyString></emptyString>
-                 </XmlEmptyStringsInputOutput>
+                 </XmlEmptyStringsRequest>
                  """,
            bodyMediaType: "application/xml",
            headers: {
@@ -463,9 +470,9 @@ apply XmlEmptyStrings @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlEmptyStringsInputOutput>
+              <XmlEmptyStringsResponse>
                   <emptyString></emptyString>
-              </XmlEmptyStringsInputOutput>
+              </XmlEmptyStringsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -482,9 +489,9 @@ apply XmlEmptyStrings @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlEmptyStringsInputOutput>
+              <XmlEmptyStringsResponse>
                   <emptyString/>
-              </XmlEmptyStringsInputOutput>
+              </XmlEmptyStringsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -497,15 +504,15 @@ apply XmlEmptyStrings @httpResponseTests([
     }
 ])
 
-structure XmlEmptyStringsInputOutput {
-    emptyString: String
-}
-
 /// Blobs are base64 encoded
 @http(uri: "/XmlBlobs", method: "POST")
 operation XmlBlobs {
-    input: XmlBlobsInputOutput,
-    output: XmlBlobsInputOutput
+    input := {
+        data: Blob
+    }
+    output := {
+        data: Blob
+    }
 }
 
 apply XmlBlobs @httpRequestTests([
@@ -516,9 +523,9 @@ apply XmlBlobs @httpRequestTests([
         method: "POST",
         uri: "/XmlBlobs",
         body: """
-              <XmlBlobsInputOutput>
+              <XmlBlobsRequest>
                   <data>dmFsdWU=</data>
-              </XmlBlobsInputOutput>
+              </XmlBlobsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -537,9 +544,9 @@ apply XmlBlobs @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlBlobsInputOutput>
+              <XmlBlobsResponse>
                   <data>dmFsdWU=</data>
-              </XmlBlobsInputOutput>
+              </XmlBlobsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -555,8 +562,12 @@ apply XmlBlobs @httpResponseTests([
 @http(uri: "/XmlEmptyBlobs", method: "POST")
 @tags(["client-only"])
 operation XmlEmptyBlobs {
-    input: XmlBlobsInputOutput,
-    output: XmlBlobsInputOutput
+    input := {
+        data: Blob
+    }
+    output := {
+        data: Blob
+    }
 }
 
 apply XmlEmptyBlobs @httpResponseTests([
@@ -566,9 +577,9 @@ apply XmlEmptyBlobs @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlBlobsInputOutput>
+              <XmlBlobsRequest>
                   <data></data>
-              </XmlBlobsInputOutput>
+              </XmlBlobsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -585,9 +596,9 @@ apply XmlEmptyBlobs @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlBlobsInputOutput>
+              <XmlBlobsResponse>
                   <data/>
-              </XmlBlobsInputOutput>
+              </XmlBlobsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -600,17 +611,13 @@ apply XmlEmptyBlobs @httpResponseTests([
     }
 ])
 
-structure XmlBlobsInputOutput {
-    data: Blob
-}
-
 /// This tests how timestamps are serialized, including using the
 /// default format of date-time and various @timestampFormat trait
 /// values.
 @http(uri: "/XmlTimestamps", method: "POST")
 operation XmlTimestamps {
-    input: XmlTimestampsInputOutput,
-    output: XmlTimestampsInputOutput
+    input := with [XmlTimestampsInputOutput] {}
+    output := with [XmlTimestampsInputOutput] {}
 }
 
 apply XmlTimestamps @httpRequestTests([
@@ -621,9 +628,9 @@ apply XmlTimestamps @httpRequestTests([
         method: "POST",
         uri: "/XmlTimestamps",
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsRequest>
                   <normal>2014-04-29T18:30:38Z</normal>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -640,9 +647,9 @@ apply XmlTimestamps @httpRequestTests([
         method: "POST",
         uri: "/XmlTimestamps",
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsRequest>
                   <dateTime>2014-04-29T18:30:38Z</dateTime>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -659,9 +666,9 @@ apply XmlTimestamps @httpRequestTests([
         method: "POST",
         uri: "/XmlTimestamps",
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsRequest>
                   <dateTimeOnTarget>2014-04-29T18:30:38Z</dateTimeOnTarget>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -678,9 +685,9 @@ apply XmlTimestamps @httpRequestTests([
         method: "POST",
         uri: "/XmlTimestamps",
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsRequest>
                   <epochSeconds>1398796238</epochSeconds>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -697,9 +704,9 @@ apply XmlTimestamps @httpRequestTests([
         method: "POST",
         uri: "/XmlTimestamps",
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsRequest>
                   <epochSecondsOnTarget>1398796238</epochSecondsOnTarget>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -716,9 +723,9 @@ apply XmlTimestamps @httpRequestTests([
         method: "POST",
         uri: "/XmlTimestamps",
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsRequest>
                   <httpDate>Tue, 29 Apr 2014 18:30:38 GMT</httpDate>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -735,9 +742,9 @@ apply XmlTimestamps @httpRequestTests([
         method: "POST",
         uri: "/XmlTimestamps",
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsRequest>
                   <httpDateOnTarget>Tue, 29 Apr 2014 18:30:38 GMT</httpDateOnTarget>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -756,9 +763,9 @@ apply XmlTimestamps @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsResponse>
                   <normal>2014-04-29T18:30:38Z</normal>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -774,9 +781,9 @@ apply XmlTimestamps @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsResponse>
                   <dateTime>2014-04-29T18:30:38Z</dateTime>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -792,9 +799,9 @@ apply XmlTimestamps @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsResponse>
                   <dateTimeOnTarget>2014-04-29T18:30:38Z</dateTimeOnTarget>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -810,9 +817,9 @@ apply XmlTimestamps @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsResponse>
                   <epochSeconds>1398796238</epochSeconds>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -828,9 +835,9 @@ apply XmlTimestamps @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsResponse>
                   <epochSecondsOnTarget>1398796238</epochSecondsOnTarget>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -846,9 +853,9 @@ apply XmlTimestamps @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsResponse>
                   <httpDate>Tue, 29 Apr 2014 18:30:38 GMT</httpDate>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -864,9 +871,9 @@ apply XmlTimestamps @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlTimestampsInputOutput>
+              <XmlTimestampsResponse>
                   <httpDateOnTarget>Tue, 29 Apr 2014 18:30:38 GMT</httpDateOnTarget>
-              </XmlTimestampsInputOutput>
+              </XmlTimestampsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -878,6 +885,7 @@ apply XmlTimestamps @httpResponseTests([
     },
 ])
 
+@mixin
 structure XmlTimestampsInputOutput {
     normal: Timestamp,
 
@@ -901,8 +909,8 @@ structure XmlTimestampsInputOutput {
 @idempotent
 @http(uri: "/XmlEnums", method: "PUT")
 operation XmlEnums {
-    input: XmlEnumsInputOutput,
-    output: XmlEnumsInputOutput
+    input := with [XmlEnumsInputOutput] {}
+    output := with [XmlEnumsInputOutput] {}
 }
 
 apply XmlEnums @httpRequestTests([
@@ -913,7 +921,7 @@ apply XmlEnums @httpRequestTests([
         method: "PUT",
         uri: "/XmlEnums",
         body: """
-              <XmlEnumsInputOutput>
+              <XmlEnumsRequest>
                   <fooEnum1>Foo</fooEnum1>
                   <fooEnum2>0</fooEnum2>
                   <fooEnum3>1</fooEnum3>
@@ -935,7 +943,7 @@ apply XmlEnums @httpRequestTests([
                           <value>0</value>
                       </entry>
                   </fooEnumMap>
-              </XmlEnumsInputOutput>
+              </XmlEnumsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -962,7 +970,7 @@ apply XmlEnums @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlEnumsInputOutput>
+              <XmlEnumsResponse>
                   <fooEnum1>Foo</fooEnum1>
                   <fooEnum2>0</fooEnum2>
                   <fooEnum3>1</fooEnum3>
@@ -984,7 +992,7 @@ apply XmlEnums @httpResponseTests([
                           <value>0</value>
                       </entry>
                   </fooEnumMap>
-              </XmlEnumsInputOutput>
+              </XmlEnumsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -1004,6 +1012,7 @@ apply XmlEnums @httpResponseTests([
     }
 ])
 
+@mixin
 structure XmlEnumsInputOutput {
     fooEnum1: FooEnum,
     fooEnum2: FooEnum,
@@ -1017,8 +1026,8 @@ structure XmlEnumsInputOutput {
 @idempotent
 @http(uri: "/XmlIntEnums", method: "PUT")
 operation XmlIntEnums {
-    input: XmlIntEnumsInputOutput,
-    output: XmlIntEnumsInputOutput
+    input := with [XmlIntEnumsInputOutput] {}
+    output := with [XmlIntEnumsInputOutput] {}
 }
 
 apply XmlIntEnums @httpRequestTests([
@@ -1029,7 +1038,7 @@ apply XmlIntEnums @httpRequestTests([
         method: "PUT",
         uri: "/XmlIntEnums",
         body: """
-              <XmlIntEnumsInputOutput>
+              <XmlIntEnumsRequest>
                   <intEnum1>1</intEnum1>
                   <intEnum2>2</intEnum2>
                   <intEnum3>3</intEnum3>
@@ -1051,7 +1060,7 @@ apply XmlIntEnums @httpRequestTests([
                           <value>2</value>
                       </entry>
                   </intEnumMap>
-              </XmlIntEnumsInputOutput>
+              </XmlIntEnumsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -1078,7 +1087,7 @@ apply XmlIntEnums @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlIntEnumsInputOutput>
+              <XmlIntEnumsResponse>
                   <intEnum1>1</intEnum1>
                   <intEnum2>2</intEnum2>
                   <intEnum3>3</intEnum3>
@@ -1100,7 +1109,7 @@ apply XmlIntEnums @httpResponseTests([
                           <value>2</value>
                       </entry>
                   </intEnumMap>
-              </XmlIntEnumsInputOutput>
+              </XmlIntEnumsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -1120,6 +1129,7 @@ apply XmlIntEnums @httpResponseTests([
     }
 ])
 
+@mixin
 structure XmlIntEnumsInputOutput {
     intEnum1: IntegerEnum,
     intEnum2: IntegerEnum,
@@ -1133,8 +1143,12 @@ structure XmlIntEnumsInputOutput {
 @idempotent
 @http(uri: "/RecursiveShapes", method: "PUT")
 operation RecursiveShapes {
-    input: RecursiveShapesInputOutput,
-    output: RecursiveShapesInputOutput
+    input := {
+        nested: RecursiveShapesInputOutputNested1
+    }
+    output := {
+        nested: RecursiveShapesInputOutputNested1
+    }
 }
 
 apply RecursiveShapes @httpRequestTests([
@@ -1145,7 +1159,7 @@ apply RecursiveShapes @httpRequestTests([
         method: "PUT",
         uri: "/RecursiveShapes",
         body: """
-              <RecursiveShapesInputOutput>
+              <RecursiveShapesRequest>
                   <nested>
                       <foo>Foo1</foo>
                       <nested>
@@ -1158,7 +1172,7 @@ apply RecursiveShapes @httpRequestTests([
                           </recursiveMember>
                       </nested>
                   </nested>
-              </RecursiveShapesInputOutput>
+              </RecursiveShapesRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -1188,7 +1202,7 @@ apply RecursiveShapes @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <RecursiveShapesInputOutput>
+              <RecursiveShapesResponse>
                   <nested>
                       <foo>Foo1</foo>
                       <nested>
@@ -1201,7 +1215,7 @@ apply RecursiveShapes @httpResponseTests([
                           </recursiveMember>
                       </nested>
                   </nested>
-              </RecursiveShapesInputOutput>
+              </RecursiveShapesResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -1224,10 +1238,6 @@ apply RecursiveShapes @httpResponseTests([
     }
 ])
 
-structure RecursiveShapesInputOutput {
-    nested: RecursiveShapesInputOutputNested1
-}
-
 structure RecursiveShapesInputOutputNested1 {
     foo: String,
     nested: RecursiveShapesInputOutputNested2
@@ -1241,8 +1251,8 @@ structure RecursiveShapesInputOutputNested2 {
 // XML namespace
 @http(uri: "/XmlNamespaces", method: "POST")
 operation XmlNamespaces {
-    input: XmlNamespacesInputOutput,
-    output: XmlNamespacesInputOutput
+    input := with [XmlNamespacesInputOutput] {}
+    output := with [XmlNamespacesInputOutput] {}
 }
 
 apply XmlNamespaces @httpRequestTests([
@@ -1253,7 +1263,7 @@ apply XmlNamespaces @httpRequestTests([
         method: "POST",
         uri: "/XmlNamespaces",
         body: """
-              <XmlNamespacesInputOutput xmlns="http://foo.com">
+              <XmlNamespacesRequest xmlns="http://foo.com">
                   <nested>
                       <foo xmlns:baz="http://baz.com">Foo</foo>
                       <values xmlns="http://qux.com">
@@ -1261,7 +1271,7 @@ apply XmlNamespaces @httpRequestTests([
                           <member xmlns="http://bux.com">Baz</member>
                       </values>
                   </nested>
-              </XmlNamespacesInputOutput>
+              </XmlNamespacesRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -1286,7 +1296,7 @@ apply XmlNamespaces @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlNamespacesInputOutput xmlns="http://foo.com">
+              <XmlNamespacesResponse xmlns="http://foo.com">
                   <nested>
                       <foo xmlns:baz="http://baz.com">Foo</foo>
                       <values xmlns="http://qux.com">
@@ -1294,7 +1304,7 @@ apply XmlNamespaces @httpResponseTests([
                           <member xmlns="http://bux.com">Baz</member>
                       </values>
                   </nested>
-              </XmlNamespacesInputOutput>
+              </XmlNamespacesResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -1312,6 +1322,7 @@ apply XmlNamespaces @httpResponseTests([
     }
 ])
 
+@mixin
 @xmlNamespace(uri: "http://foo.com")
 structure XmlNamespacesInputOutput {
     nested: XmlNamespaceNested
diff --git a/smithy-aws-protocol-tests/model/restXml/document-unions.smithy b/smithy-aws-protocol-tests/model/restXml/document-unions.smithy
index d9cc39a30..f852e6a35 100644
--- a/smithy-aws-protocol-tests/model/restXml/document-unions.smithy
+++ b/smithy-aws-protocol-tests/model/restXml/document-unions.smithy
@@ -2,6 +2,8 @@
 // in the payload of HTTP requests and responses.
 
 $version: "2.0"
+$operationInputSuffix: "Request"
+$operationOutputSuffix: "Response"
 
 namespace aws.protocoltests.restxml
 
@@ -14,8 +16,12 @@ use smithy.test#httpResponseTests
 @idempotent
 @http(uri: "/XmlUnions", method: "PUT")
 operation XmlUnions {
-    input: XmlUnionsInputOutput,
-    output: XmlUnionsInputOutput
+    input := {
+        unionValue: XmlUnionShape
+    }
+    output := {
+        unionValue: XmlUnionShape
+    }
 }
 
 apply XmlUnions @httpRequestTests([
@@ -26,7 +32,7 @@ apply XmlUnions @httpRequestTests([
         method: "PUT",
         uri: "/XmlUnions",
         body: """
-              <XmlUnionsInputOutput>
+              <XmlUnionsRequest>
                   <unionValue>
                      <structValue>
                         <stringValue>string</stringValue>
@@ -39,7 +45,7 @@ apply XmlUnions @httpRequestTests([
                         <doubleValue>6.5</doubleValue>
                      </structValue>
                   </unionValue>
-              </XmlUnionsInputOutput>
+              </XmlUnionsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -67,11 +73,11 @@ apply XmlUnions @httpRequestTests([
         method: "PUT",
         uri: "/XmlUnions",
         body: """
-              <XmlUnionsInputOutput>
+              <XmlUnionsRequest>
                  <unionValue>
                     <stringValue>some string</stringValue>
                  </unionValue>
-              </XmlUnionsInputOutput>
+              </XmlUnionsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -90,11 +96,11 @@ apply XmlUnions @httpRequestTests([
         method: "PUT",
         uri: "/XmlUnions",
         body: """
-              <XmlUnionsInputOutput>
+              <XmlUnionsRequest>
                  <unionValue>
                     <booleanValue>true</booleanValue>
                  </unionValue>
-              </XmlUnionsInputOutput>
+              </XmlUnionsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -113,13 +119,13 @@ apply XmlUnions @httpRequestTests([
         method: "PUT",
         uri: "/XmlUnions",
         body: """
-              <XmlUnionsInputOutput>
+              <XmlUnionsRequest>
                  <unionValue>
                     <unionValue>
                        <booleanValue>true</booleanValue>
                     </unionValue>
                  </unionValue>
-              </XmlUnionsInputOutput>
+              </XmlUnionsRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -142,7 +148,7 @@ apply XmlUnions @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlUnionsInputOutput>
+              <XmlUnionsResponse>
                   <unionValue>
                      <structValue>
                         <stringValue>string</stringValue>
@@ -155,7 +161,7 @@ apply XmlUnions @httpResponseTests([
                         <doubleValue>6.5</doubleValue>
                      </structValue>
                   </unionValue>
-              </XmlUnionsInputOutput>
+              </XmlUnionsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -182,11 +188,11 @@ apply XmlUnions @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlUnionsInputOutput>
+              <XmlUnionsResponse>
                  <unionValue>
                     <stringValue>some string</stringValue>
                  </unionValue>
-              </XmlUnionsInputOutput>
+              </XmlUnionsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -204,11 +210,11 @@ apply XmlUnions @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlUnionsInputOutput>
+              <XmlUnionsResponse>
                  <unionValue>
                     <booleanValue>true</booleanValue>
                  </unionValue>
-              </XmlUnionsInputOutput>
+              </XmlUnionsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -226,13 +232,13 @@ apply XmlUnions @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlUnionsInputOutput>
+              <XmlUnionsResponse>
                  <unionValue>
                     <unionValue>
                        <booleanValue>true</booleanValue>
                     </unionValue>
                  </unionValue>
-              </XmlUnionsInputOutput>
+              </XmlUnionsResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -248,10 +254,6 @@ apply XmlUnions @httpResponseTests([
     },
 ])
 
-structure XmlUnionsInputOutput {
-    unionValue: XmlUnionShape,
-}
-
 union XmlUnionShape {
     stringValue: String,
     booleanValue: Boolean,
diff --git a/smithy-aws-protocol-tests/model/restXml/document-xml-attributes.smithy b/smithy-aws-protocol-tests/model/restXml/document-xml-attributes.smithy
index bb8c80085..1b5177de3 100644
--- a/smithy-aws-protocol-tests/model/restXml/document-xml-attributes.smithy
+++ b/smithy-aws-protocol-tests/model/restXml/document-xml-attributes.smithy
@@ -1,6 +1,8 @@
 // This file defines test cases that serialize XML attributes.
 
 $version: "2.0"
+$operationInputSuffix: "Request"
+$operationOutputSuffix: "Response"
 
 namespace aws.protocoltests.restxml
 
@@ -12,8 +14,8 @@ use smithy.test#httpResponseTests
 @idempotent
 @http(uri: "/XmlAttributes", method: "PUT")
 operation XmlAttributes {
-    input: XmlAttributesInputOutput,
-    output: XmlAttributesInputOutput
+    input := with [XmlAttributesInputOutput] {}
+    output := with [XmlAttributesInputOutput] {}
 }
 
 apply XmlAttributes @httpRequestTests([
@@ -24,9 +26,9 @@ apply XmlAttributes @httpRequestTests([
         method: "PUT",
         uri: "/XmlAttributes",
         body: """
-              <XmlAttributesInputOutput test="test">
+              <XmlAttributesRequest test="test">
                   <foo>hi</foo>
-              </XmlAttributesInputOutput>
+              </XmlAttributesRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -44,9 +46,9 @@ apply XmlAttributes @httpRequestTests([
         method: "PUT",
         uri: "/XmlAttributes",
         body: """
-              <XmlAttributesInputOutput test="&lt;test&amp;mock&gt;">
+              <XmlAttributesRequest test="&lt;test&amp;mock&gt;">
                   <foo>hi</foo>
-              </XmlAttributesInputOutput>
+              </XmlAttributesRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -66,9 +68,9 @@ apply XmlAttributes @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlAttributesInputOutput test="test">
+              <XmlAttributesResponse test="test">
                   <foo>hi</foo>
-              </XmlAttributesInputOutput>
+              </XmlAttributesResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -81,6 +83,7 @@ apply XmlAttributes @httpResponseTests([
     }
 ])
 
+@mixin
 structure XmlAttributesInputOutput {
     foo: String,
 
@@ -93,8 +96,14 @@ structure XmlAttributesInputOutput {
 @idempotent
 @http(uri: "/XmlAttributesOnPayload", method: "PUT")
 operation XmlAttributesOnPayload {
-    input: XmlAttributesOnPayloadInputOutput,
-    output: XmlAttributesOnPayloadInputOutput
+    input := {
+        @httpPayload
+        payload: XmlAttributesPayloadRequest
+    }
+    output := {
+        @httpPayload
+        payload: XmlAttributesPayloadResponse
+    }
 }
 
 apply XmlAttributesOnPayload @httpRequestTests([
@@ -105,9 +114,9 @@ apply XmlAttributesOnPayload @httpRequestTests([
         method: "PUT",
         uri: "/XmlAttributesOnPayload",
         body: """
-              <XmlAttributesInputOutput test="test">
+              <XmlAttributesPayloadRequest test="test">
                   <foo>hi</foo>
-              </XmlAttributesInputOutput>
+              </XmlAttributesPayloadRequest>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -129,9 +138,9 @@ apply XmlAttributesOnPayload @httpResponseTests([
         protocol: restXml,
         code: 200,
         body: """
-              <XmlAttributesInputOutput test="test">
+              <XmlAttributesPayloadResponse test="test">
                   <foo>hi</foo>
-              </XmlAttributesInputOutput>
+              </XmlAttributesPayloadResponse>
               """,
         bodyMediaType: "application/xml",
         headers: {
@@ -146,7 +155,6 @@ apply XmlAttributesOnPayload @httpResponseTests([
     }
 ])
 
-structure XmlAttributesOnPayloadInputOutput {
-    @httpPayload
-    payload: XmlAttributesInputOutput
-}
+structure XmlAttributesPayloadRequest with [XmlAttributesInputOutput] {}
+
+structure XmlAttributesPayloadResponse with [XmlAttributesInputOutput] {}
diff --git a/smithy-aws-protocol-tests/model/restXml/endpoints.smithy b/smithy-aws-protocol-tests/model/restXml/endpoints.smithy
index 3e99ca911..4af3a8470 100644
--- a/smithy-aws-protocol-tests/model/restXml/endpoints.smithy
+++ b/smithy-aws-protocol-tests/model/restXml/endpoints.smithy
@@ -2,6 +2,7 @@
 // trait and other features that modify the host.
 
 $version: "2.0"
+$operationInputSuffix: "Request"
 
 namespace aws.protocoltests.restxml
 
@@ -26,7 +27,6 @@ use smithy.test#httpRequestTests
 @http(uri: "/EndpointOperation", method: "POST")
 operation EndpointOperation {}
 
-
 @httpRequestTests([
     {
         id: "RestXmlEndpointTraitWithHostLabel",
@@ -38,9 +38,9 @@ operation EndpointOperation {}
         method: "POST",
         uri: "/EndpointWithHostLabelOperation",
         body: """
-              <HostLabelInput>
+              <EndpointWithHostLabelOperationRequest>
                   <label>bar</label>
-              </HostLabelInput>
+              </EndpointWithHostLabelOperationRequest>
               """,
         bodyMediaType: "application/xml",
         host: "example.com",
@@ -53,13 +53,11 @@ operation EndpointOperation {}
 @endpoint(hostPrefix: "foo.{label}.")
 @http(uri: "/EndpointWithHostLabelOperation", method: "POST")
 operation EndpointWithHostLabelOperation {
-    input: HostLabelInput,
-}
-
-structure HostLabelInput {
-    @required
-    @hostLabel
-    label: String,
+    input := {
+        @required
+        @hostLabel
+        label: String
+    }
 }
 
 @httpRequestTests([
diff --git a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/auth/SigV4ATraitTest.java b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/auth/SigV4ATraitTest.java
new file mode 100644
index 000000000..5a2b0abc4
--- /dev/null
+++ b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/auth/SigV4ATraitTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.traits.auth;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Optional;
+import org.junit.jupiter.api.Test;
+import software.amazon.smithy.model.node.Node;
+import software.amazon.smithy.model.node.ObjectNode;
+import software.amazon.smithy.model.node.StringNode;
+import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.Trait;
+import software.amazon.smithy.model.traits.TraitFactory;
+
+public class SigV4ATraitTest {
+    private static final String MOCK_SIGNING_NAME = "mocksigningname";
+    private static final ShapeId MOCK_TARGET = ShapeId.from("ns.qux#foo");
+
+    @Test
+    public void loadsTrait() {
+        Node node = ObjectNode.builder()
+            .withMember("name", StringNode.from(MOCK_SIGNING_NAME))
+            .build();
+        TraitFactory provider = TraitFactory.createServiceFactory();
+        Optional<Trait> trait = provider.createTrait(SigV4ATrait.ID, MOCK_TARGET, node);
+
+        assertTrue(trait.isPresent());
+        assertThat(trait.get(), instanceOf(SigV4ATrait.class));
+        SigV4ATrait sigv4aTrait = (SigV4ATrait) trait.get();
+        assertFalse(sigv4aTrait.getName().isEmpty());
+        assertThat(sigv4aTrait.getName(), equalTo(MOCK_SIGNING_NAME));
+        assertThat(sigv4aTrait.toNode(), equalTo(node));
+        assertThat(sigv4aTrait.toBuilder().build(), equalTo(sigv4aTrait));
+    }
+}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-service-mismatch.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-service-mismatch.errors
new file mode 100644
index 000000000..4c070e372
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-service-mismatch.errors
@@ -0,0 +1,1 @@
+[WARNING] smithy.example#InvalidService: Value for `aws.api#service$arnNamespace` "invalidservice" and value for `aws.auth#sigv4$name` "signingname" SHOULD match. | SigV4Traits
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-service-mismatch.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-service-mismatch.smithy
new file mode 100644
index 000000000..50b9e51b6
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-service-mismatch.smithy
@@ -0,0 +1,12 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use aws.api#service
+use aws.auth#sigv4
+
+@service(sdkId: "servicename")
+@sigv4(name: "signingname")
+service InvalidService {
+    version: "2020-07-02"
+}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-sigv4a-mismatch.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-sigv4a-mismatch.errors
new file mode 100644
index 000000000..0e4a9d570
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-sigv4a-mismatch.errors
@@ -0,0 +1,1 @@
+[WARNING] smithy.example#InvalidService: Value for `aws.auth#sigv4$name` "sigv4signingname" and value for `aws.auth#sigv4a$name` "sigv4asigningname" SHOULD match. | SigV4Traits
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-sigv4a-mismatch.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-sigv4a-mismatch.smithy
new file mode 100644
index 000000000..803527de3
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-sigv4a-mismatch.smithy
@@ -0,0 +1,13 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4a, sigv4])
+@sigv4(name: "sigv4signingname")
+@sigv4a(name: "sigv4asigningname")
+service InvalidService {
+    version: "2020-07-02"
+}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-empty-name.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-empty-name.errors
new file mode 100644
index 000000000..6965fa031
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-empty-name.errors
@@ -0,0 +1,2 @@
+[ERROR] smithy.example#InvalidService: Error validating trait `aws.auth#sigv4a`.name: String value provided for `aws.auth#sigv4a$name` must be >= 1 characters, but the provided value is only 0 characters. | TraitValue
+[WARNING] smithy.example#InvalidService: Value for `aws.auth#sigv4$name` "signingname" and value for `aws.auth#sigv4a$name` "" SHOULD match. | SigV4Traits
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-empty-name.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-empty-name.smithy
new file mode 100644
index 000000000..ef73c04f0
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-empty-name.smithy
@@ -0,0 +1,13 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@auth([sigv4a, sigv4])
+@sigv4(name: "signingname")
+@sigv4a(name: "")
+service InvalidService {
+    version: "2020-07-02"
+}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-no-sigv4.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-no-sigv4.errors
new file mode 100644
index 000000000..cef5bbdd1
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-no-sigv4.errors
@@ -0,0 +1,1 @@
+[ERROR] smithy.example#InvalidService: Trait `aws.auth#sigv4a` cannot be applied to `smithy.example#InvalidService`. This trait may only be applied to shapes that match the following selector: service[trait|aws.auth#sigv4] | TraitTarget
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-no-sigv4.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-no-sigv4.smithy
new file mode 100644
index 000000000..13b5f3644
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-no-sigv4.smithy
@@ -0,0 +1,10 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use aws.auth#sigv4a
+
+@sigv4a(name: "signingname")
+service InvalidService {
+    version: "2020-07-02"
+}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-service-mismatch.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-service-mismatch.errors
new file mode 100644
index 000000000..469c9815d
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-service-mismatch.errors
@@ -0,0 +1,2 @@
+[WARNING] smithy.example#InvalidService: Value for `aws.api#service$arnNamespace` "invalidservice" and value for `aws.auth#sigv4a$name` "signingname" SHOULD match. | SigV4Traits
+[WARNING] smithy.example#InvalidService: Value for `aws.auth#sigv4$name` "invalidservice" and value for `aws.auth#sigv4a$name` "signingname" SHOULD match. | SigV4Traits
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-service-mismatch.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-service-mismatch.smithy
new file mode 100644
index 000000000..1c7c3a864
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-service-mismatch.smithy
@@ -0,0 +1,15 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use aws.api#service
+use aws.auth#sigv4
+use aws.auth#sigv4a
+
+@service(sdkId: "servicename")
+@auth([sigv4a, sigv4])
+@sigv4(name: "invalidservice")
+@sigv4a(name: "signingname")
+service InvalidService {
+    version: "2020-07-02"
+}
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/OperationIndexTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/OperationIndexTest.java
index a77709070..435abfa32 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/OperationIndexTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/OperationIndexTest.java
@@ -23,15 +23,18 @@ import static org.hamcrest.Matchers.is;
 
 import java.util.Collections;
 import java.util.Optional;
+import java.util.Set;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 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.Shape;
 import software.amazon.smithy.model.shapes.ShapeId;
 import software.amazon.smithy.model.shapes.StructureShape;
 import software.amazon.smithy.model.traits.UnitTypeTrait;
+import software.amazon.smithy.utils.SetUtils;
 
 public class OperationIndexTest {
 
@@ -40,7 +43,7 @@ public class OperationIndexTest {
     @BeforeAll
     public static void before() {
         model = Model.assembler()
-                .addImport(OperationIndexTest.class.getResource("operation-index-test.json"))
+                .addImport(OperationIndexTest.class.getResource("operation-index-test.smithy"))
                 .assemble()
                 .unwrap();
     }
@@ -103,6 +106,18 @@ public class OperationIndexTest {
         assertThat(opIndex.isInputStructure(output), is(false));
     }
 
+    @Test
+    public void getsInputBindings() {
+        OperationIndex index = OperationIndex.of(model);
+        Set<OperationShape> actual = index.getInputBindings(ShapeId.from("ns.foo#Input"));
+        Set<OperationShape> expected = SetUtils.of(
+                model.expectShape(ShapeId.from("ns.foo#B"), OperationShape.class),
+                model.expectShape(ShapeId.from("ns.foo#C"), OperationShape.class)
+        );
+        assertThat(actual, equalTo(expected));
+        assertThat(index.getInputBindings(ShapeId.from("ns.foo#Output")), empty());
+    }
+
     @Test
     public void determinesIfShapeIsUsedAsOutput() {
         OperationIndex opIndex = OperationIndex.of(model);
@@ -113,6 +128,18 @@ public class OperationIndexTest {
         assertThat(opIndex.isOutputStructure(input), is(false));
     }
 
+    @Test
+    public void getsOutputBindings() {
+        OperationIndex index = OperationIndex.of(model);
+        Set<OperationShape> actual = index.getOutputBindings(ShapeId.from("ns.foo#Output"));
+        Set<OperationShape> expected = SetUtils.of(
+                model.expectShape(ShapeId.from("ns.foo#B"), OperationShape.class),
+                model.expectShape(ShapeId.from("ns.foo#C"), OperationShape.class)
+        );
+        assertThat(actual, equalTo(expected));
+        assertThat(index.getOutputBindings(ShapeId.from("ns.foo#Input")), empty());
+    }
+
     @Test
     public void getsOperationErrorsAndInheritedErrors() {
         OperationIndex opIndex = OperationIndex.of(model);
@@ -130,4 +157,21 @@ public class OperationIndexTest {
         assertThat(opIndex.getErrors(b), containsInAnyOrder(error1, error2));
         assertThat(opIndex.getErrors(service, b), containsInAnyOrder(error1, error2, common1, common2));
     }
+
+    @Test
+    public void getsErrorBindings() {
+        OperationIndex index = OperationIndex.of(model);
+        Set<Shape> actual = index.getErrorBindings(ShapeId.from("ns.foo#CommonError1"));
+        Set<Shape> expected = SetUtils.of(
+                model.expectShape(ShapeId.from("ns.foo#MyService"), ServiceShape.class),
+                model.expectShape(ShapeId.from("ns.foo#C"), OperationShape.class)
+        );
+        assertThat(actual, equalTo(expected));
+
+        actual = index.getErrorBindings(ShapeId.from("ns.foo#Error1"));
+        expected = SetUtils.of(model.expectShape(ShapeId.from("ns.foo#B"), OperationShape.class));
+        assertThat(actual, equalTo(expected));
+
+        assertThat(index.getOutputBindings(ShapeId.from("ns.foo#UnusedError")), empty());
+    }
 }
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/traits/ExamplesTraitTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/traits/ExamplesTraitTest.java
index 37c3dab47..761e0fd99 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/traits/ExamplesTraitTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/traits/ExamplesTraitTest.java
@@ -17,6 +17,7 @@ package software.amazon.smithy.model.traits;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -27,6 +28,8 @@ import software.amazon.smithy.model.node.ArrayNode;
 import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.node.ObjectNode;
 import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.ExamplesTrait.ErrorExample;
+import software.amazon.smithy.model.traits.ExamplesTrait.Example;
 
 public class ExamplesTraitTest {
     @Test
@@ -81,4 +84,24 @@ public class ExamplesTraitTest {
         assertFalse(serialized.get(1).get().asObjectNode().get().getMember("allowConstraintErrors").isPresent());
         assertTrue(serialized.get(2).get().asObjectNode().get().getMember("allowConstraintErrors").isPresent());
     }
+
+    @Test
+    public void exampleEqualsWorks() {
+        ObjectNode input = Node.objectNode().withMember("a", Node.from("b"));
+        ObjectNode output = Node.objectNode().withMember("c", Node.from("d"));
+        ErrorExample errorExample1 = ErrorExample.builder().shapeId(ShapeId.from("smithy.example#FooError")).content(Node.objectNode()
+                .withMember("e", Node.from("f"))).build();
+        ErrorExample errorExample2 = ErrorExample.builder().shapeId(ShapeId.from("smithy.example#FooError")).content(Node.objectNode()
+                .withMember("e", Node.from("f"))).build();
+        ErrorExample errorExample3 = ErrorExample.builder().shapeId(ShapeId.from("smithy.example#FooError")).content(Node.objectNode()
+                .withMember("g", Node.from("h"))).build();
+        Example example1 = Example.builder().title("foo").documentation("docs").input(input).output(output).error(errorExample1).build();
+        Example example2 = Example.builder().title("foo").documentation("docs").input(input).output(output).error(errorExample2).build();
+        Example example3 = Example.builder().title("foo").documentation("docs").input(input).output(output).error(errorExample3).build();
+        assertThat(errorExample1, equalTo(errorExample2));
+        assertThat(errorExample1, not(equalTo(errorExample3)));
+        assertThat(example1, equalTo(example2));
+        assertThat(example1, not(equalTo(example3)));
+    }
+
 }
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/operation-index-test.json b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/operation-index-test.json
deleted file mode 100644
index 77f427877..000000000
--- a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/operation-index-test.json
+++ /dev/null
@@ -1,81 +0,0 @@
-{
-    "smithy": "2.0",
-    "shapes": {
-        "ns.foo#MyService": {
-            "type": "service",
-            "version": "2017-01-17",
-            "operations": [
-                {
-                    "target": "ns.foo#A"
-                },
-                {
-                    "target": "ns.foo#B"
-                }
-            ],
-            "errors": [
-                {
-                    "target": "ns.foo#CommonError1"
-                },
-                {
-                    "target": "ns.foo#CommonError2"
-                }
-            ]
-        },
-        "ns.foo#A": {
-            "type": "operation",
-            "traits": {
-                "smithy.api#readonly": {}
-            }
-        },
-        "ns.foo#B": {
-            "type": "operation",
-            "input": {
-                "target": "ns.foo#Input"
-            },
-            "output": {
-                "target": "ns.foo#Output"
-            },
-            "errors": [
-                {
-                    "target": "ns.foo#Error1"
-                },
-                {
-                    "target": "ns.foo#Error2"
-                }
-            ],
-            "traits": {
-                "smithy.api#readonly": {}
-            }
-        },
-        "ns.foo#Input": {
-            "type": "structure"
-        },
-        "ns.foo#Output": {
-            "type": "structure"
-        },
-        "ns.foo#Error1": {
-            "type": "structure",
-            "traits": {
-                "smithy.api#error": "client"
-            }
-        },
-        "ns.foo#Error2": {
-            "type": "structure",
-            "traits": {
-                "smithy.api#error": "server"
-            }
-        },
-        "ns.foo#CommonError1": {
-            "type": "structure",
-            "traits": {
-                "smithy.api#error": "server"
-            }
-        },
-        "ns.foo#CommonError2": {
-            "type": "structure",
-            "traits": {
-                "smithy.api#error": "server"
-            }
-        }
-    }
-}
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/operation-index-test.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/operation-index-test.smithy
new file mode 100644
index 000000000..d633de21b
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/operation-index-test.smithy
@@ -0,0 +1,58 @@
+$version: "2.0"
+
+namespace ns.foo
+
+service MyService {
+    version: "2017-01-17"
+    operations: [
+        A
+        B
+    ]
+    errors: [
+        CommonError1
+        CommonError2
+    ]
+}
+
+@readonly
+operation A {
+    input: Unit
+    output: Unit
+}
+
+@readonly
+operation B {
+    input: Input
+    output: Output
+    errors: [
+        Error1
+        Error2
+    ]
+}
+
+operation C {
+    input: Input
+    output: Output,
+    errors: [
+        CommonError1
+    ]
+}
+
+@error("server")
+structure CommonError1 {}
+
+@error("server")
+structure CommonError2 {}
+
+@error("client")
+structure Error1 {}
+
+@error("server")
+structure Error2 {}
+
+@error("client")
+structure UnusedError {}
+
+structure Input {}
+
+structure Output {}