Repository: smithy-lang/smithy
Pull request #2671 (2025-06-19 22:42:01)

Trait codegen: less restrictive namespace check
Allow namespaces like `smithy4s`, but not `smithy` or `smithy.api` or other sub-`smithy` namespaces.


#### Background
* What do these changes do? 
* Why are they important?

#### Testing
* How did you test these changes?

#### Links
* Links to additional context, if necessary
* Issue #, if applicable (see [here](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) for a list of keywords to use for linking issues)

---
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/CHANGELOG.md b/CHANGELOG.md
index ea620bb15..b3ca9edc7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,37 @@
 # Smithy Changelog
 
+## 1.60.3 (2025-06-27)
+
+### Bug Fixes
+
+* Fixed incorrect operation name in URI for `NonQueryCompatibleOperation`. ([#2684](https://github.com/smithy-lang/smithy/pull/2684/))
+* Fixed incorrect property name in protocol test bodies. ([#2686](https://github.com/smithy-lang/smithy/pull/2686))
+
+## 1.60.2 (2025-06-26)
+
+### Bug Fixes
+
+* Added model discovery args to smithy select. ([#2680](https://github.com/smithy-lang/smithy/pull/2680))
+* Fixed `NonQueryCompatible` to remove the bogus empty JSON body and media-type. ([#2681](https://github.com/smithy-lang/smithy/pull/2681))
+* Reduced query error uniqueness validation severity. ([#2682](https://github.com/smithy-lang/smithy/pull/2682))
+
+## 1.60.1 (2025-06-25)
+
+### Bug Fixes
+
+* Fixed body of awsQueryCompatible test. ([#2677](https://github.com/smithy-lang/smithy/pull/2677))
+
+## 1.60.0 (2025-06-23)
+
+### Bug Fixes
+
+* Added ASM to the relocated dependencies. ([#2676](https://github.com/smithy-lang/smithy/pull/2676))
+
+### Features
+
+* Added protocol tests for `@awsQueryCompatible`. ([#2672](https://github.com/smithy-lang/smithy/pull/2672))
+* Added validation to ensure query errors are unique. ([#2674](https://github.com/smithy-lang/smithy/pull/2674))
+
 ## 1.59.0 (2025-06-16)
 
 ### Bug Fixes
diff --git a/VERSION b/VERSION
index bb120e876..8f16209d1 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-1.59.0
+1.60.3
diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/protocols/QueryErrorCodeValidator.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/protocols/QueryErrorCodeValidator.java
new file mode 100644
index 000000000..5db5d8902
--- /dev/null
+++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/protocols/QueryErrorCodeValidator.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package software.amazon.smithy.aws.traits.protocols;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.knowledge.TopDownIndex;
+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.validation.AbstractValidator;
+import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.utils.SmithyInternalApi;
+
+/**
+ * Emits a warning if two or more error shapes share the same error code.
+ */
+@SmithyInternalApi
+public class QueryErrorCodeValidator extends AbstractValidator {
+
+    @Override
+    public List<ValidationEvent> validate(Model model) {
+        List<ValidationEvent> events = Collections.emptyList();
+        for (ServiceShape service : model.getServiceShapes()) {
+            if (service.hasTrait(AwsQueryCompatibleTrait.class) || service.hasTrait(AwsQueryTrait.class)
+                    || service.hasTrait(Ec2QueryTrait.class)) {
+                List<ValidationEvent> serviceEvents = validateService(model, service);
+                if (!serviceEvents.isEmpty()) {
+                    if (events.isEmpty()) {
+                        events = new ArrayList<>();
+                    }
+                    events.addAll(serviceEvents);
+                }
+            }
+        }
+        return events;
+    }
+
+    private List<ValidationEvent> validateService(Model model, ServiceShape service) {
+        TopDownIndex index = TopDownIndex.of(model);
+        Set<ShapeId> errors = new HashSet<>(service.getErrors());
+        for (OperationShape operation : index.getContainedOperations(service)) {
+            errors.addAll(operation.getErrors());
+        }
+
+        Map<String, Set<String>> errorCodeBindings = new HashMap<>();
+        for (ShapeId errorId : errors) {
+            String errorCode = errorId.getName();
+            Shape errorShape = model.expectShape(errorId);
+            if (errorShape.hasTrait(AwsQueryErrorTrait.class)) {
+                AwsQueryErrorTrait trait = errorShape.expectTrait(AwsQueryErrorTrait.class);
+                errorCode = trait.getCode();
+            }
+            errorCodeBindings.computeIfAbsent(errorCode, k -> new TreeSet<>()).add(errorId.toString());
+        }
+        List<ValidationEvent> events = Collections.emptyList();
+        for (Map.Entry<String, Set<String>> entry : errorCodeBindings.entrySet()) {
+            if (entry.getValue().size() == 1) {
+                continue;
+            }
+            String errorCode = entry.getKey();
+            String shapes = String.join(", ", entry.getValue());
+            if (events.isEmpty()) {
+                events = new ArrayList<>();
+            }
+            events.add(warning(service,
+                    String.format("Multiple error shapes with the error code `%s` found: [%s]. This error code "
+                            + "ambiguity will result in the wrong error shape being deserialized when running the "
+                            + "query protocol. This pain will be carried forward when migrating to a new protocol "
+                            + "when using the awsQueryCompatible trait.", errorCode, shapes)));
+        }
+        return events;
+    }
+}
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 133106fc8..76bf941d9 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
@@ -5,6 +5,7 @@ software.amazon.smithy.aws.traits.SdkServiceIdValidator
 software.amazon.smithy.aws.traits.clientendpointdiscovery.ClientEndpointDiscoveryValidator
 software.amazon.smithy.aws.traits.protocols.ProtocolHttpValidator
 software.amazon.smithy.aws.traits.EventSourceValidator
+software.amazon.smithy.aws.traits.protocols.QueryErrorCodeValidator
 software.amazon.smithy.aws.traits.protocols.ProtocolHttpPayloadValidator
 software.amazon.smithy.aws.traits.tagging.ServiceTaggingValidator
 software.amazon.smithy.aws.traits.tagging.TagEnabledServiceValidator
diff --git a/smithy-cli/build.gradle.kts b/smithy-cli/build.gradle.kts
index f838a7150..d28c4a933 100644
--- a/smithy-cli/build.gradle.kts
+++ b/smithy-cli/build.gradle.kts
@@ -191,6 +191,7 @@ tasks {
         relocate("org.apache", "software.amazon.smithy.cli.shaded.apache")
         relocate("org.sonatype", "software.amazon.smithy.cli.shaded.sonatype")
         relocate("org.codehaus", "software.amazon.smithy.cli.shaded.codehaus")
+        relocate("org.objectweb", "software.amazon.smithy.cli.shaded.objectweb")
 
         // If other javax packages are ever pulled in, we'll need to update this list. This is more deliberate about
         // what's shaded to ensure that things like javax.net.ssl.SSLSocketFactory are not inadvertently shaded.
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SelectCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SelectCommand.java
index cc59387d4..9542423ec 100644
--- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SelectCommand.java
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/SelectCommand.java
@@ -62,6 +62,7 @@ final class SelectCommand implements Command {
     @Override
     public int execute(Arguments arguments, Env env) {
         arguments.addReceiver(new ConfigOptions());
+        arguments.addReceiver(new DiscoveryOptions());
         arguments.addReceiver(new BuildOptions());
         arguments.addReceiver(new Options());
 
diff --git a/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/TraitCodegenSettings.java b/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/TraitCodegenSettings.java
index 188b59666..a72b5f5bd 100644
--- a/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/TraitCodegenSettings.java
+++ b/smithy-trait-codegen/src/main/java/software/amazon/smithy/traitcodegen/TraitCodegenSettings.java
@@ -55,8 +55,8 @@ public final class TraitCodegenSettings {
     ) {
         this.packageName = Objects.requireNonNull(packageName);
         this.smithyNamespace = Objects.requireNonNull(smithyNamespace);
-        if (smithyNamespace.startsWith(SMITHY_API_NAMESPACE)) {
-            throw new IllegalArgumentException("The `smithy` namespace is reserved.");
+        if (smithyNamespace.equals(SMITHY_API_NAMESPACE) || smithyNamespace.startsWith(SMITHY_API_NAMESPACE + ".")) {
+            throw new IllegalArgumentException("The `smithy` namespace and its sub-namespaces are reserved.");
         }
         this.headerLines = Objects.requireNonNull(headerLines);
         this.excludeTags = Objects.requireNonNull(excludeTags);


Test patch:
diff --git a/smithy-aws-protocol-tests/model/aws-config.smithy b/smithy-aws-protocol-tests/model/aws-config.smithy
index 321956d62..1679c0944 100644
--- a/smithy-aws-protocol-tests/model/aws-config.smithy
+++ b/smithy-aws-protocol-tests/model/aws-config.smithy
@@ -109,3 +109,30 @@ enum RetryMode {
     STANDARD = "standard"
     ADAPTIVE = "adaptive"
 }
+
+/// Vendor params to use when making assertions about error code or query
+/// error type.
+structure ErrorCodeParams {
+    /// The error code exposed to the customer.
+    ///
+    /// If the SDK exposes the error code to customers, this value MUST be
+    /// asserted to match the exposed error code.
+    ///
+    /// This is modifiable by the awsQueryError trait. For services with
+    /// awsQueryCompatible, it will be exposed in the `x-amzn-query-error`
+    /// header which takes the form `Code;Fault`. This value represents
+    /// the `Code` portion.
+    @required
+    code: String
+
+    /// The query error type exposed to the customer.
+    ///
+    /// If the SDK exposes the query error type to customers, this value MUST be
+    /// asserted to match the exposed query error type.
+    ///
+    /// This is modifiable by the awsQueryError trait. For services with
+    /// awsQueryCompatible, it will be exposed in the `x-amzn-query-error`
+    /// header which takes the form `Code;Fault`. This value represents
+    /// the `Fault` portion.
+    type: String
+}
diff --git a/smithy-aws-protocol-tests/model/awsJson1_0/main.smithy b/smithy-aws-protocol-tests/model/awsJson1_0/main.smithy
index f7314548b..4dcabeaec 100644
--- a/smithy-aws-protocol-tests/model/awsJson1_0/main.smithy
+++ b/smithy-aws-protocol-tests/model/awsJson1_0/main.smithy
@@ -5,8 +5,7 @@ namespace aws.protocoltests.json10
 use aws.api#service
 use aws.auth#sigv4
 use aws.protocols#awsJson1_0
-use smithy.test#httpRequestTests
-use smithy.test#httpResponseTests
+use aws.protocols#awsQueryCompatible
 
 @service(sdkId: "JSON RPC 10")
 @sigv4(name: "jsonrpc10")
@@ -42,5 +41,20 @@ service JsonRpc10 {
         OperationWithRequiredMembers,
         OperationWithNestedStructure
         OperationWithRequiredMembersWithDefaults
+
+        QueryIncompatibleOperation
+    ]
+}
+
+
+@service(sdkId: "Query Compatible JSON RPC 10")
+@sigv4(name: "querycompatiblejsonrpc10")
+@awsJson1_0
+@title("Query Compatible Json 1.0 Protocol Service")
+@awsQueryCompatible
+service QueryCompatibleJsonRpc10 {
+    version: "2020-07-14",
+    operations: [
+        QueryCompatibleOperation
     ]
 }
diff --git a/smithy-aws-protocol-tests/model/awsJson1_0/query-compatible.smithy b/smithy-aws-protocol-tests/model/awsJson1_0/query-compatible.smithy
new file mode 100644
index 000000000..57fe57fc6
--- /dev/null
+++ b/smithy-aws-protocol-tests/model/awsJson1_0/query-compatible.smithy
@@ -0,0 +1,94 @@
+$version: "2.0"
+
+namespace aws.protocoltests.json10
+
+use aws.protocols#awsJson1_0
+use aws.protocols#awsQueryError
+use aws.protocoltests.config#ErrorCodeParams
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+
+@httpRequestTests([
+    {
+        id: "NonQueryCompatibleAwsJson10ForbidsQueryModeHeader"
+        documentation: "The query mode header MUST NOT be set on non-query-compatible services."
+        protocol: awsJson1_0
+        method: "POST"
+        headers: { "Content-Type": "application/x-amz-json-1.0", "X-Amz-Target": "JsonRpc10.QueryIncompatibleOperation" }
+        uri: "/"
+        body: "{}"
+        bodyMediaType: "application/json"
+    }
+])
+@idempotent
+operation QueryIncompatibleOperation {}
+
+@idempotent
+operation QueryCompatibleOperation {
+    errors: [
+        NoCustomCodeError
+        CustomCodeError
+    ]
+}
+
+apply QueryCompatibleOperation @httpRequestTests([
+    {
+        id: "QueryCompatibleAwsJson10CborSendsQueryModeHeader"
+        documentation: "Clients for query-compatible services MUST send the x-amzn-query-mode header."
+        protocol: awsJson1_0
+        method: "POST"
+        headers: { "Content-Type": "application/x-amz-json-1.0", "x-amzn-query-mode": "true", "X-Amz-Target": "QueryCompatibleJsonRpc10.QueryCompatibleOperation" }
+        uri: "/"
+        body: "{}"
+        bodyMediaType: "application/json"
+    }
+])
+
+@error("client")
+structure NoCustomCodeError {
+    message: String
+}
+
+apply NoCustomCodeError @httpResponseTests([
+    {
+        id: "QueryCompatibleAwsJson10NoCustomCodeError"
+        documentation: "Parses simple errors with no query error code"
+        protocol: awsJson1_0
+        params: { message: "Hi" }
+        code: 400
+        headers: { "Content-Type": "application/x-amz-json-1.0" }
+        body: """
+            {
+                "__type": "aws.protocoltests.json10#NoCustomCodeError",
+                "message": "Hi"
+            }"""
+        bodyMediaType: "application/json"
+        vendorParamsShape: ErrorCodeParams
+        vendorParams: { code: "NoCustomCodeError" }
+    }
+])
+
+@awsQueryError(code: "Customized", httpResponseCode: 402)
+@error("client")
+structure CustomCodeError {
+    message: String
+}
+
+apply CustomCodeError @httpResponseTests([
+    {
+        id: "QueryCompatibleAwsJson10CustomCodeError"
+        documentation: "Parses simple errors with query error code"
+        protocol: awsJson1_0
+        params: { message: "Hi" }
+        code: 400
+        headers: { "Content-Type": "application/x-amz-json-1.0", "x-amzn-query-error": "Customized;Sender" }
+        body: """
+            {
+                "__type": "aws.protocoltests.json10#CustomCodeError",
+                "message": "Hi"
+            }"""
+        bodyMediaType: "application/json"
+        vendorParamsShape: ErrorCodeParams
+        vendorParams: { code: "Customized", type: "Sender" }
+    }
+])
diff --git a/smithy-aws-protocol-tests/model/awsQuery/xml-errors.smithy b/smithy-aws-protocol-tests/model/awsQuery/xml-errors.smithy
index 3d64b5626..c21141487 100644
--- a/smithy-aws-protocol-tests/model/awsQuery/xml-errors.smithy
+++ b/smithy-aws-protocol-tests/model/awsQuery/xml-errors.smithy
@@ -29,6 +29,7 @@ namespace aws.protocoltests.query
 
 use aws.protocols#awsQueryError
 use aws.protocols#awsQuery
+use aws.protocoltests.config#ErrorCodeParams
 use smithy.test#httpResponseTests
 
 /// This operation has three possible return values:
@@ -97,6 +98,11 @@ apply InvalidGreeting @httpResponseTests([
               </ErrorResponse>
               """,
         bodyMediaType: "application/xml",
+        vendorParamsShape: ErrorCodeParams
+        vendorParams: {
+            code: "InvalidGreeting"
+            type: "Sender"
+        }
     }
 ])
 
@@ -136,6 +142,11 @@ apply ComplexError @httpResponseTests([
               </ErrorResponse>
               """,
         bodyMediaType: "application/xml",
+        vendorParamsShape: ErrorCodeParams
+        vendorParams: {
+            code: "ComplexError"
+            type: "Sender"
+        }
     }
 ])
 
@@ -175,5 +186,10 @@ apply CustomCodeError @httpResponseTests([
               </ErrorResponse>
               """,
         bodyMediaType: "application/xml",
+        vendorParamsShape: ErrorCodeParams
+        vendorParams: {
+            code: "Customized"
+            type: "Sender"
+        }
     }
 ])
diff --git a/smithy-aws-protocol-tests/model/ec2Query/xml-errors.smithy b/smithy-aws-protocol-tests/model/ec2Query/xml-errors.smithy
index fec1cd73b..0dc823790 100644
--- a/smithy-aws-protocol-tests/model/ec2Query/xml-errors.smithy
+++ b/smithy-aws-protocol-tests/model/ec2Query/xml-errors.smithy
@@ -23,6 +23,7 @@ $version: "2.0"
 namespace aws.protocoltests.ec2
 
 use aws.protocols#ec2Query
+use aws.protocoltests.config#ErrorCodeParams
 use smithy.test#httpResponseTests
 
 /// This operation has three possible return values:
@@ -91,6 +92,10 @@ apply InvalidGreeting @httpResponseTests([
         params: {
             Message: "Hi"
         },
+        vendorParamsShape: ErrorCodeParams
+        vendorParams: {
+            code: "InvalidGreeting"
+        }
     }
 ])
 
@@ -132,6 +137,10 @@ apply ComplexError @httpResponseTests([
               </Response>
               """,
         bodyMediaType: "application/xml",
+        vendorParamsShape: ErrorCodeParams
+        vendorParams: {
+            code: "ComplexError"
+        }
     }
 ])
 
diff --git a/smithy-aws-protocol-tests/model/rpcv2Cbor/non-query-compatible.smithy b/smithy-aws-protocol-tests/model/rpcv2Cbor/non-query-compatible.smithy
new file mode 100644
index 000000000..d9f00e723
--- /dev/null
+++ b/smithy-aws-protocol-tests/model/rpcv2Cbor/non-query-compatible.smithy
@@ -0,0 +1,34 @@
+$version: "2"
+
+namespace aws.protocoltests.rpcv2cbor
+
+use aws.api#service
+use aws.auth#sigv4
+use smithy.protocols#rpcv2Cbor
+use smithy.test#httpRequestTests
+
+@service(sdkId: "Non Query Compatible RpcV2 Protocol")
+@sigv4(name: "nonquerycompatiblerpcv2protocol")
+@rpcv2Cbor
+@title("Non Query Compatible RpcV2 Protocol Service")
+service NonQueryCompatibleRpcV2Protocol {
+    version: "2025-06-20"
+    operations: [
+        NonQueryCompatibleOperation
+    ]
+}
+
+@httpRequestTests([
+    {
+        id: "NonQueryCompatibleRpcV2CborForbidsQueryModeHeader"
+        documentation: "The query mode header MUST NOT be set on non-query-compatible services."
+        protocol: rpcv2Cbor
+        method: "POST"
+        headers: { "smithy-protocol": "rpc-v2-cbor", Accept: "application/cbor" }
+        forbidHeaders: ["x-amzn-query-mode"]
+        uri: "/service/NonQueryCompatibleRpcV2Protocol/operation/NonQueryCompatibleOperation"
+        body: ""
+    }
+])
+@idempotent
+operation NonQueryCompatibleOperation {}
diff --git a/smithy-aws-protocol-tests/model/rpcv2Cbor/query-compatible.smithy b/smithy-aws-protocol-tests/model/rpcv2Cbor/query-compatible.smithy
new file mode 100644
index 000000000..e41478fbe
--- /dev/null
+++ b/smithy-aws-protocol-tests/model/rpcv2Cbor/query-compatible.smithy
@@ -0,0 +1,88 @@
+$version: "2"
+
+namespace aws.protocoltests.rpcv2cbor
+
+use aws.api#service
+use aws.auth#sigv4
+use aws.protocols#awsQueryCompatible
+use aws.protocols#awsQueryError
+use aws.protocoltests.config#ErrorCodeParams
+use smithy.protocols#rpcv2Cbor
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+
+
+@service(sdkId: "Query Compatible RpcV2 Protocol")
+@sigv4(name: "querycompatiblerpcv2protocol")
+@rpcv2Cbor
+@title("Query Compatible RpcV2 Protocol Service")
+@awsQueryCompatible
+service QueryCompatibleRpcV2Protocol {
+    version: "2025-06-20"
+    operations: [
+        QueryCompatibleOperation
+    ]
+}
+
+
+@idempotent
+operation QueryCompatibleOperation {
+    errors: [
+        NoCustomCodeError
+        CustomCodeError
+    ]
+}
+
+apply QueryCompatibleOperation @httpRequestTests([
+    {
+        id: "QueryCompatibleRpcV2CborSendsQueryModeHeader"
+        documentation: "Clients for query-compatible services MUST send the x-amzn-query-mode header."
+        protocol: rpcv2Cbor
+        method: "POST"
+        headers: { "smithy-protocol": "rpc-v2-cbor", Accept: "application/cbor", "x-amzn-query-mode": "true" }
+        forbidHeaders: ["Content-Type", "X-Amz-Target"]
+        uri: "/service/QueryCompatibleRpcV2Protocol/operation/QueryCompatibleOperation"
+        body: ""
+    }
+])
+
+@error("client")
+structure NoCustomCodeError {
+    message: String
+}
+
+apply NoCustomCodeError @httpResponseTests([
+    {
+        id: "QueryCompatibleRpcV2CborNoCustomCodeError"
+        documentation: "Parses simple RpcV2 CBOR errors with no query error code"
+        protocol: rpcv2Cbor
+        params: { message: "Hi" }
+        code: 400
+        headers: { "smithy-protocol": "rpc-v2-cbor", "Content-Type": "application/cbor" }
+        body: "uQACZl9fdHlwZXgtYXdzLnByb3RvY29sdGVzdHMucnBjdjJjYm9yI05vQ3VzdG9tQ29kZUVycm9yZ21lc3NhZ2ViSGk="
+        bodyMediaType: "application/cbor"
+        vendorParamsShape: ErrorCodeParams
+        vendorParams: { code: "NoCustomCodeError" }
+    }
+])
+
+@awsQueryError(code: "Customized", httpResponseCode: 402)
+@error("client")
+structure CustomCodeError {
+    message: String
+}
+
+apply CustomCodeError @httpResponseTests([
+    {
+        id: "QueryCompatibleRpcV2CborCustomCodeError"
+        documentation: "Parses simple RpcV2 CBOR errors with query error code"
+        protocol: rpcv2Cbor
+        params: { message: "Hi" }
+        code: 400
+        headers: { "smithy-protocol": "rpc-v2-cbor", "Content-Type": "application/cbor", "x-amzn-query-error": "Customized;Sender" }
+        body: "uQACZl9fdHlwZXgrYXdzLnByb3RvY29sdGVzdHMucnBjdjJjYm9yI0N1c3RvbUNvZGVFcnJvcmdtZXNzYWdlYkhp"
+        bodyMediaType: "application/cbor"
+        vendorParamsShape: ErrorCodeParams
+        vendorParams: { code: "Customized", type: "Sender" }
+    }
+])
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/query-errors-must-be-unique.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/query-errors-must-be-unique.errors
new file mode 100644
index 000000000..19809b6d2
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/query-errors-must-be-unique.errors
@@ -0,0 +1,2 @@
+[WARNING] smithy.example#QueryCompatibleService: Multiple error shapes with the error code `ResourceNotFound` found: [smithy.example#DocumentNotFound, smithy.example#ResourceNotFound]. This error code ambiguity will result in the wrong error shape being deserialized when running the query protocol. This pain will be carried forward when migrating to a new protocol when using the awsQueryCompatible trait. | QueryErrorCode
+[WARNING] smithy.example#QueryService: Multiple error shapes with the error code `ResourceNotFound` found: [smithy.example#DocumentNotFound, smithy.example#ResourceNotFound]. This error code ambiguity will result in the wrong error shape being deserialized when running the query protocol. This pain will be carried forward when migrating to a new protocol when using the awsQueryCompatible trait. | QueryErrorCode
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/query-errors-must-be-unique.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/query-errors-must-be-unique.smithy
new file mode 100644
index 000000000..c6052402e
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/protocols/query-errors-must-be-unique.smithy
@@ -0,0 +1,39 @@
+$version: "2.0"
+
+namespace smithy.example
+
+use aws.protocols#awsJson1_0
+use aws.protocols#awsQuery
+use aws.protocols#awsQueryCompatible
+use aws.protocols#awsQueryError
+
+@awsQueryCompatible
+@awsJson1_0
+service QueryCompatibleService {
+    version: "2020-02-05"
+    errors: [
+        DocumentNotFound
+        ResourceNotFound
+    ]
+}
+
+@awsQueryError(code: "ResourceNotFound", httpResponseCode: 400)
+@error("client")
+structure DocumentNotFound {
+    message: String
+}
+
+@error("client")
+structure ResourceNotFound {
+    message: String
+}
+
+@awsQuery
+@xmlNamespace(uri: "https://example.com/")
+service QueryService {
+    version: "2020-02-05"
+    errors: [
+        DocumentNotFound
+        ResourceNotFound
+    ]
+}