Repository: smithy-lang/smithy
Pull request #2295 (2024-05-22 15:40:58)

Add operationContextParams to RulesetParameterValidator
#### Background
#2266 added support for the new `operationContextParams` trait, but missed adding support for it to the `RulesetParameterValidator`.   Rulesets that bound a parameter to the service model only through an `operationContextParameter` would trigger validation errors that Parameter "exists in ruleset but not in service model".    In addition, type checking for inconsistent parameters and type mismatches between `operationContextParams` and the bound endpoint parameter were not being done.

#### Testing
*New and existing tests

#### Links
* See: #2266

---
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/docs/source-1.0/spec/aws/aws-json.rst.template b/docs/source-1.0/spec/aws/aws-json.rst.template
index ed8744312..641970472 100644
--- a/docs/source-1.0/spec/aws/aws-json.rst.template
+++ b/docs/source-1.0/spec/aws/aws-json.rst.template
@@ -50,7 +50,7 @@ The |quoted shape name| protocol uses the following headers:
     * - ``Content-Length``
       - true
       - The standard ``Content-Length`` header defined by
-        `RFC 7230 Section 3.3.2`_.
+        :rfc:`9110#section-8.6`.
     * - ``X-Amz-Target``
       - true for requests
       - The value of this header is the :token:`shape name <smithy:Identifier>` of the
@@ -207,5 +207,3 @@ These compliance tests define a model that is used to define test cases and
 the expected serialized HTTP requests and responses for each case.
 
 *TODO: Add event stream handling specifications.*
-
-.. _`RFC 7230 Section 3.3.2`: https://tools.ietf.org/html/rfc7230#section-3.3.2
diff --git a/docs/source-1.0/spec/aws/aws-query-serialization.rst.template b/docs/source-1.0/spec/aws/aws-query-serialization.rst.template
index 4b11f1fbb..fac8711b5 100644
--- a/docs/source-1.0/spec/aws/aws-query-serialization.rst.template
+++ b/docs/source-1.0/spec/aws/aws-query-serialization.rst.template
@@ -97,5 +97,3 @@ to the input's key.
     * - ``union``
       - A union is serialized identically to a ``structure`` shape, but only a
         single member can be set to a non-null value.
-
-.. _`RFC 7230 Section 3.3.2`: https://tools.ietf.org/html/rfc7230#section-3.3.2
diff --git a/docs/source-1.0/spec/core/http-traits.rst b/docs/source-1.0/spec/core/http-traits.rst
index 61d2368e9..2ae3190bf 100644
--- a/docs/source-1.0/spec/core/http-traits.rst
+++ b/docs/source-1.0/spec/core/http-traits.rst
@@ -14,8 +14,8 @@ and error structures are considered when serializing HTTP messages.
 
 .. important::
 
-    Violating `HTTP specifications`_ or relying on poorly-supported HTTP
-    functionality when defining HTTP bindings will limit interoperability
+    Violating :rfc:`HTTP specifications <9110>` or relying on poorly-supported
+    HTTP functionality when defining HTTP bindings will limit interoperability
     and likely lead to undefined behavior across Smithy implementations. For
     example, avoid defining GET/DELETE requests with payloads, defining
     response payloads for operations with a 204/205 status, etc.
@@ -106,9 +106,8 @@ method
 The ``method`` property defines the HTTP method of the operation (e.g., "GET",
 "PUT", "POST", "DELETE", "PATCH", etc). Smithy will use this value literally
 and will perform no validation on the method. The ``method`` value SHOULD
-match the ``operation`` production rule of :rfc:`7230#appendix-B`. This
-property does not influence the safety or idempotency characteristics of an
-operation.
+match one of the definitions found in :rfc:`9110#section-9.3`. This property
+does not influence the safety or idempotency characteristics of an operation.
 
 
 .. _http-uri:
@@ -117,7 +116,7 @@ uri
 ---
 
 The ``uri`` property defines the *request-target* of the operation in
-*origin-form* as defined in :rfc:`7230#section-5.3.1`. The URI is a simple
+*origin-form* as defined in :rfc:`9112#section-3.2.1`. The URI is a simple
 pattern that Smithy uses to match HTTP requests to operations and to bind
 components of the request URI to fields in the operations's input structure.
 :dfn:`Patterns` consist of literal characters that MUST be matched in the
@@ -525,9 +524,8 @@ Trait selector
     ``structure`` member that targets a list of these types.
 Value type
     ``string`` value defining a valid HTTP header field name according to
-    :rfc:`section 3.2 of RFC7230 <7230#section-3.2>`. The value MUST NOT be
-    empty and MUST be case-insensitively unique across all other members of
-    the structure.
+    :rfc:`9110#section-5.1`. The value MUST NOT be empty and MUST be
+    case-insensitively unique across all other members of the structure.
 Conflicts with
    :ref:`httpLabel-trait`,
    :ref:`httpQuery-trait`,
@@ -546,7 +544,7 @@ Conflicts with
 * ``string`` values with a :ref:`mediaType-trait` are always base64 encoded.
 * ``timestamp`` values are serialized using the ``http-date``
   format by default, as defined in the ``IMF-fixdate`` production of
-  :rfc:`7231#section-7.1.1.1`. The :ref:`timestampFormat-trait` MAY be used
+  :rfc:`9110#section-5.6.7`. The :ref:`timestampFormat-trait` MAY be used
   to use a custom serialization format.
 
 .. rubric:: Do not put too much data in HTTP headers
@@ -954,10 +952,10 @@ the body of the response.
 
 .. rubric:: Do not put too much data in the query string
 
-While there is no limit placed on the length of an `HTTP request line`_,
-many HTTP client and server implementations enforce limits in practice.
-Carefully consider the maximum allowed length of each member that is bound to
-an HTTP query string or path.
+While there is no limit placed on the length of an
+:rfc:`HTTP request line <9112#section-3>`, many HTTP client and server
+implementations enforce limits in practice. Carefully consider the maximum
+allowed length of each member that is bound to an HTTP query string or path.
 
 
 .. smithy-trait:: smithy.api#httpQueryParams
@@ -1335,5 +1333,3 @@ marked with the ``httpPayload`` trait:
 
 
 .. _percent-encoded: https://tools.ietf.org/html/rfc3986#section-2.1
-.. _HTTP request line: https://tools.ietf.org/html/rfc7230.html#section-3.1.1
-.. _HTTP specifications: https://datatracker.ietf.org/doc/html/rfc7230
diff --git a/docs/source-1.0/spec/core/model.rst b/docs/source-1.0/spec/core/model.rst
index bd5c4e6ae..78f1659dc 100644
--- a/docs/source-1.0/spec/core/model.rst
+++ b/docs/source-1.0/spec/core/model.rst
@@ -2020,7 +2020,7 @@ The following example defines the ``PutForecast`` operation.
 .. rubric:: Put semantics
 
 The semantics of a ``put`` lifecycle operation are similar to the semantics
-of a HTTP PUT method as described in :rfc:`section 4.3.4 of [RFC7231] <7231#section-4.3.4>`:
+of a HTTP PUT method as described in :rfc:`section 4.3.4 of [RFC9110] <9110#section-9.3.4>`:
 
   The PUT method requests that the state of the target resource be
   created or replaced ...
diff --git a/docs/source-1.0/spec/core/protocol-traits.rst b/docs/source-1.0/spec/core/protocol-traits.rst
index e420498b0..61c1af42b 100644
--- a/docs/source-1.0/spec/core/protocol-traits.rst
+++ b/docs/source-1.0/spec/core/protocol-traits.rst
@@ -309,7 +309,7 @@ Smithy defines the following built-in timestamp formats:
         values that contain offsets gracefully by normalizing them to UTC.
     * - http-date
       - An HTTP date as defined by the ``IMF-fixdate`` production in
-        :rfc:`7231#section-7.1.1.1` (for example,
+        :rfc:`9110#section-5.6.7` (for example,
         ``Tue, 29 Apr 2014 18:30:38 GMT``). A deserializer that encounters an
         ``http-date`` timestamp with fractional precision SHOULD fail to
         deserialize the value (for example, an HTTP server SHOULD return a 400
diff --git a/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst b/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst
index 40479383a..46b54ffa2 100644
--- a/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst
+++ b/docs/source-2.0/additional-specs/protocols/smithy-rpc-v2.rst
@@ -238,7 +238,7 @@ headers for requests:
         is ``application/vnd.amazon.eventstream``.
     * - ``Content-Length``
       - Conditional
-      - The standard ``Content-Length`` header defined by :rfc:`7230#section-3.3.2`.
+      - The standard ``Content-Length`` header defined by :rfc:`9110#section-8.6`.
         For event streaming requests, this MUST NOT be set.
     * - ``Accept``
       - Conditional
@@ -303,7 +303,7 @@ headers for responses:
         is ``application/vnd.amazon.eventstream``.
     * - ``Content-Length``
       - Conditional
-      - The standard ``Content-Length`` header defined by :rfc:`7230#section-3.3.2`.
+      - The standard ``Content-Length`` header defined by :rfc:`9110#section-8.6`.
         For event streaming requests, this SHOULD NOT be set.
 
 
diff --git a/docs/source-2.0/aws/protocols/aws-json.rst.template b/docs/source-2.0/aws/protocols/aws-json.rst.template
index e77914073..0e28b73a1 100644
--- a/docs/source-2.0/aws/protocols/aws-json.rst.template
+++ b/docs/source-2.0/aws/protocols/aws-json.rst.template
@@ -50,7 +50,7 @@ The |quoted shape name| protocol uses the following headers:
     * - ``Content-Length``
       - true
       - The standard ``Content-Length`` header defined by
-        :rfc:`7230#section-3.3.2`.
+        :rfc:`9110#section-8.6`.
     * - ``X-Amz-Target``
       - true for requests
       - The value of this header is the :token:`shape name <smithy:Identifier>` of the
diff --git a/docs/source-2.0/guides/model-translations/converting-to-openapi.rst b/docs/source-2.0/guides/model-translations/converting-to-openapi.rst
index 91746b394..bbeb9a93c 100644
--- a/docs/source-2.0/guides/model-translations/converting-to-openapi.rst
+++ b/docs/source-2.0/guides/model-translations/converting-to-openapi.rst
@@ -1550,6 +1550,29 @@ additionalAllowedCorsHeaders (``[string]``)
             }
         }
 
+
+.. _generate-openapi-apigateway-setting-syncCorsPreflightIntegration:
+
+syncCorsPreflightIntegration (``boolean``)
+    Set to true to sync CORS preflight integration request templates with all possible content-types
+    from other methods within the same path resource.
+
+    .. code-block:: json
+        :caption: smithy-build.json
+
+        {
+            "version": "1.0",
+            "plugins": {
+                "openapi": {
+                    "service": "example.weather#Weather",
+                    "syncCorsPreflightIntegration": true
+                }
+            }
+        }
+
+    With this enabled, the `integration's passthroughBehavior`_ for CORS preflight integration
+    will be set to ``never``.
+
 Binary types
 ============
 
@@ -1648,6 +1671,10 @@ additions during the OpenAPI conversion:
 * Adds static CORS response headers to API Gateway "gateway" responses.  These are added only when
   no gateway responses are defined in the OpenAPI model.
 
+.. note::
+    If :ref:`syncCorsPreflightIntegration <generate-openapi-apigateway-setting-syncCorsPreflightIntegration>` is
+    set to ``true``, the CORS preflight `integration's passthroughBehavior`_ will be set to ``never`` and the integration's
+    request templates will be synced with all possible content-types from other methods within the same path resource.
 
 .. _authorizers:
 
@@ -2037,4 +2064,5 @@ The conversion process is highly extensible through
 .. _Lambda authorizers: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-authorizer.html
 .. _API Gateway's API key usage plans: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-usage-plans.html
 .. _OpenAPI specification extension: https://spec.openapis.org/oas/v3.1.0#specification-extensions
+.. _integration's passthroughBehavior: https://docs.aws.amazon.com/apigateway/latest/developerguide/integration-passthrough-behaviors.html
 .. _gradle installed: https://gradle.org/install/
diff --git a/docs/source-2.0/spec/behavior-traits.rst b/docs/source-2.0/spec/behavior-traits.rst
index 7ada59fea..5fdfdb834 100644
--- a/docs/source-2.0/spec/behavior-traits.rst
+++ b/docs/source-2.0/spec/behavior-traits.rst
@@ -6,7 +6,7 @@ Idempotency
 ===========
 
 Operations marked with the :ref:`readonly-trait` or :ref:`idempotent-trait`
-are considered idempotent as defined in :rfc:`7231#section-4.2.2`. Operations
+are considered idempotent as defined in :rfc:`9110#section-9.2.2`. Operations
 that contain a top-level input member marked with the :ref:`idempotencytoken-trait`
 that are provided a token for the member are also considered idempotent. All
 other operations SHOULD be considered unsafe to retry unless the response to
diff --git a/docs/source-2.0/spec/http-bindings.rst b/docs/source-2.0/spec/http-bindings.rst
index fa00f8de0..b177a5454 100644
--- a/docs/source-2.0/spec/http-bindings.rst
+++ b/docs/source-2.0/spec/http-bindings.rst
@@ -14,7 +14,7 @@ and error structures are considered when serializing HTTP messages.
 
 .. important::
 
-    Violating :rfc:`HTTP specifications <7230>` or relying on poorly-supported
+    Violating :rfc:`HTTP specifications <9110>` or relying on poorly-supported
     HTTP functionality when defining HTTP bindings will limit interoperability
     and likely lead to undefined behavior across Smithy implementations. For
     example, avoid defining GET/DELETE requests with payloads, defining
@@ -107,9 +107,8 @@ method
 The ``method`` property defines the HTTP method of the operation (e.g., "GET",
 "PUT", "POST", "DELETE", "PATCH", etc). Smithy will use this value literally
 and will perform no validation on the method. The ``method`` value SHOULD
-match the ``operation`` production rule of :rfc:`7230#appendix-B`. This
-property does not influence the safety or idempotency characteristics of an
-operation.
+match one of the definitions found in :rfc:`9110#section-9.3`. This property
+does not influence the safety or idempotency characteristics of an operation.
 
 
 .. _http-uri:
@@ -118,7 +117,7 @@ uri
 ---
 
 The ``uri`` property defines the *request-target* of the operation in
-*origin-form* as defined in :rfc:`7230#section-5.3.1`. The URI is a simple
+*origin-form* as defined in :rfc:`9112#section-3.2.1`. The URI is a simple
 pattern that Smithy uses to match HTTP requests to operations and to bind
 components of the request URI to fields in the operations's input structure.
 :dfn:`Patterns` consist of literal characters that MUST be matched in the
@@ -634,9 +633,8 @@ Trait selector
     ``structure`` member that targets a list of these types.
 Value type
     ``string`` value defining a valid HTTP header field name according to
-    :rfc:`section 3.2 of RFC7230 <7230#section-3.2>`. The value MUST NOT be
-    empty and MUST be case-insensitively unique across all other members of
-    the structure.
+    :rfc:`9110#section-5.1`. The value MUST NOT be empty and MUST be
+    case-insensitively unique across all other members of the structure.
 Conflicts with
    :ref:`httpLabel-trait`,
    :ref:`httpQuery-trait`,
@@ -656,7 +654,7 @@ Conflicts with
 * ``string`` values with a :ref:`mediaType-trait` are always base64 encoded.
 * ``timestamp`` values are serialized using the ``http-date``
   format by default, as defined in the ``IMF-fixdate`` production of
-  :rfc:`7231#section-7.1.1.1`. The :ref:`timestampFormat-trait` MAY be used
+  :rfc:`9110#section-5.6.7`. The :ref:`timestampFormat-trait` MAY be used
   to use a custom serialization format.
 
 .. note::
@@ -1089,7 +1087,7 @@ is simply ignored.
 .. note::
 
     While there is no limit placed on the length of an
-    :rfc:`HTTP request line <7230#section-3.1.1>`, many HTTP client and server
+    :rfc:`HTTP request line <9112#section-3>`, many HTTP client and server
     implementations enforce limits in practice. Carefully consider the maximum
     allowed length of each member that is bound to an HTTP query string or
     path.
diff --git a/docs/source-2.0/spec/protocol-traits.rst b/docs/source-2.0/spec/protocol-traits.rst
index 9e9341043..18f9518ff 100644
--- a/docs/source-2.0/spec/protocol-traits.rst
+++ b/docs/source-2.0/spec/protocol-traits.rst
@@ -228,7 +228,7 @@ Smithy defines the following built-in timestamp formats:
         values that contain offsets gracefully by normalizing them to UTC.
     * - http-date
       - An HTTP date as defined by the ``IMF-fixdate`` production in
-        :rfc:`7231#section-7.1.1.1` (for example,
+        :rfc:`9110#section-5.6.7` (for example,
         ``Tue, 29 Apr 2014 18:30:38 GMT``). A deserializer that encounters an
         ``http-date`` timestamp with fractional precision SHOULD fail to
         deserialize the value (for example, an HTTP server SHOULD return a 400
diff --git a/docs/source-2.0/spec/service-types.rst b/docs/source-2.0/spec/service-types.rst
index 5ce6ac3d2..c5dd2bcda 100644
--- a/docs/source-2.0/spec/service-types.rst
+++ b/docs/source-2.0/spec/service-types.rst
@@ -862,7 +862,7 @@ Put semantics
 ^^^^^^^^^^^^^
 
 The semantics of a ``put`` lifecycle operation are similar to the semantics
-of a HTTP PUT method as described in :rfc:`section 4.3.4 of [RFC7231] <7231#section-4.3.4>`:
+of a HTTP PUT method as described in :rfc:`section 4.3.4 of [RFC9110] <9110#section-9.3.4>`:
 
   The PUT method requests that the state of the target resource be
   created or replaced ...
diff --git a/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddCorsPreflightIntegration.java b/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddCorsPreflightIntegration.java
index ad2eab0f1..c80458cc4 100644
--- a/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddCorsPreflightIntegration.java
+++ b/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddCorsPreflightIntegration.java
@@ -31,6 +31,7 @@ import software.amazon.smithy.aws.apigateway.traits.MockIntegrationTrait;
 import software.amazon.smithy.jsonschema.Schema;
 import software.amazon.smithy.model.Model;
 import software.amazon.smithy.model.knowledge.TopDownIndex;
+import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.node.ObjectNode;
 import software.amazon.smithy.model.shapes.OperationShape;
 import software.amazon.smithy.model.traits.CorsTrait;
@@ -45,6 +46,7 @@ import software.amazon.smithy.openapi.model.Ref;
 import software.amazon.smithy.openapi.model.ResponseObject;
 import software.amazon.smithy.utils.CaseUtils;
 import software.amazon.smithy.utils.ListUtils;
+import software.amazon.smithy.utils.SetUtils;
 
 /**
  * Adds CORS-preflight OPTIONS requests using mock API Gateway integrations.
@@ -70,6 +72,7 @@ final class AddCorsPreflightIntegration implements ApiGatewayMapper {
     private static final Logger LOGGER = Logger.getLogger(AddCorsPreflightIntegration.class.getName());
     private static final String API_GATEWAY_DEFAULT_ACCEPT_VALUE = "application/json";
     private static final String INTEGRATION_EXTENSION = "x-amazon-apigateway-integration";
+    private static final String REQUEST_TEMPLATES_KEY = "requestTemplates";
     private static final String PREFLIGHT_SUCCESS = "{\"statusCode\":200}";
 
     @Override
@@ -95,7 +98,7 @@ final class AddCorsPreflightIntegration implements ApiGatewayMapper {
         LOGGER.fine(() -> "Adding CORS-preflight OPTIONS request and API Gateway integration for " + path);
         Map<CorsHeader, String> headers = deduceCorsHeaders(context, path, pathItem, corsTrait);
         return pathItem.toBuilder()
-                .options(createPreflightOperation(path, pathItem, headers))
+                .options(createPreflightOperation(context, path, pathItem, headers))
                 .build();
     }
 
@@ -170,7 +173,7 @@ final class AddCorsPreflightIntegration implements ApiGatewayMapper {
     }
 
     private static OperationObject createPreflightOperation(
-            String path, PathItem pathItem, Map<CorsHeader, String> headers) {
+            Context<? extends Trait> context, String path, PathItem pathItem, Map<CorsHeader, String> headers) {
         return OperationObject.builder()
                 .tags(ListUtils.of("CORS"))
                 .security(Collections.emptyList())
@@ -178,7 +181,7 @@ final class AddCorsPreflightIntegration implements ApiGatewayMapper {
                 .operationId(createOperationId(path))
                 .putResponse("200", createPreflightResponse(headers))
                 .parameters(findPathParameters(pathItem))
-                .putExtension(INTEGRATION_EXTENSION, createPreflightIntegration(headers, pathItem))
+                .putExtension(INTEGRATION_EXTENSION, createPreflightIntegration(context, headers, pathItem))
                 .build();
     }
 
@@ -214,7 +217,8 @@ final class AddCorsPreflightIntegration implements ApiGatewayMapper {
         return builder.build();
     }
 
-    private static ObjectNode createPreflightIntegration(Map<CorsHeader, String> headers, PathItem pathItem) {
+    private static ObjectNode createPreflightIntegration(
+            Context<? extends Trait> context, Map<CorsHeader, String> headers, PathItem pathItem) {
         IntegrationResponse.Builder responseBuilder = IntegrationResponse.builder().statusCode("200");
 
         // Add each CORS header to the mock integration response.
@@ -222,13 +226,34 @@ final class AddCorsPreflightIntegration implements ApiGatewayMapper {
             responseBuilder.putResponseParameter("method.response.header." + e.getKey(), "'" + e.getValue() + "'");
         }
 
+        boolean isPreflightSynced = Boolean.TRUE.equals(context.getConfig().getSyncCorsPreflightIntegration());
         MockIntegrationTrait.Builder integration = MockIntegrationTrait.builder()
                 // See https://forums.aws.amazon.com/thread.jspa?threadID=256140
                 .contentHandling("CONVERT_TO_TEXT")
-                .passThroughBehavior("when_no_match")
+                // Passthrough behavior "never" will fail the request with unsupported content type more appropriately.
+                // https://docs.aws.amazon.com/apigateway/latest/developerguide/integration-passthrough-behaviors.html
+                .passThroughBehavior(isPreflightSynced ? "never" : "when_no_match")
                 .putResponse("default", responseBuilder.build())
                 .putRequestTemplate(API_GATEWAY_DEFAULT_ACCEPT_VALUE, PREFLIGHT_SUCCESS);
 
+        if (isPreflightSynced) {
+            // Adds request template for every unique Content-Type supported by all path operations.
+            // This ensures that for Content-Type(s) other than 'application/json', the entire request payload
+            // is not sent to APIGW mock integration as stipulated by 'when_no_match' passthroughBehavior.
+            // APIGW throws an error if the mock integration request does not follow a set contract,
+            // example {"statusCode":200}.
+            for (OperationObject operation : pathItem.getOperations().values()) {
+                ObjectNode extensionNode = operation.getExtension(INTEGRATION_EXTENSION)
+                        .flatMap(Node::asObjectNode)
+                        .orElse(Node.objectNode());
+                Set<String> mimeTypes = extensionNode.getObjectMember(REQUEST_TEMPLATES_KEY)
+                        .map(ObjectNode::getStringMap)
+                        .map(Map::keySet)
+                        .orElse(SetUtils.of());
+                mimeTypes.forEach(mimeType -> integration.putRequestTemplate(mimeType, PREFLIGHT_SUCCESS));
+            }
+        }
+
         // Add a request template for every mime-type of every response.
         for (OperationObject operation : pathItem.getOperations().values()) {
             for (ResponseObject response : operation.getResponses().values()) {
diff --git a/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitConflictingNameValidator.java b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitConflictingNameValidator.java
new file mode 100644
index 000000000..9ea4fa3d9
--- /dev/null
+++ b/smithy-aws-iam-traits/src/main/java/software/amazon/smithy/aws/iam/traits/IamResourceTraitConflictingNameValidator.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.iam.traits;
+
+import static software.amazon.smithy.model.validation.ValidationUtils.tickedList;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.knowledge.TopDownIndex;
+import software.amazon.smithy.model.shapes.ResourceShape;
+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;
+
+/**
+ * Ensures that there is no resource name conflict in a service closure
+ * in IAM space after processing {@link IamResourceTrait}.
+ */
+@SmithyInternalApi
+public class IamResourceTraitConflictingNameValidator extends AbstractValidator {
+    @Override
+    public List<ValidationEvent> validate(Model model) {
+        TopDownIndex topDownIndex = TopDownIndex.of(model);
+        return model.shapes(ServiceShape.class)
+                .flatMap(shape -> validateService(topDownIndex, shape).stream())
+                .collect(Collectors.toList());
+    }
+
+    private List<ValidationEvent> validateService(TopDownIndex topDownIndex, ServiceShape service) {
+        List<ValidationEvent> events = new ArrayList<>();
+        Map<String, List<ShapeId>> resourceMap = new HashMap<>();
+        for (ResourceShape resource : topDownIndex.getContainedResources(service)) {
+            String resourceName = IamResourceTrait.resolveResourceName(resource);
+            resourceMap.computeIfAbsent(resourceName, k -> new ArrayList<>()).add(resource.getId());
+        }
+        resourceMap.entrySet().stream()
+                .filter(entry -> entry.getValue().size() > 1)
+                .map(entry -> error(service, String.format(
+                        "Multiple IAM resources defined with the same IAM resource name is not allowed in a service "
+                                + "closure, but found multiple resources named `%s` in the service `%s`: [%s]",
+                        entry.getKey(), service.getId(), tickedList(entry.getValue()))))
+                .forEach(events::add);
+        return events;
+    }
+}
diff --git a/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
index 856e5dbe6..120767a57 100644
--- a/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
+++ b/smithy-aws-iam-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
@@ -1,3 +1,4 @@
 software.amazon.smithy.aws.iam.traits.ConditionKeysValidator
 software.amazon.smithy.aws.iam.traits.IamActionValidator
 software.amazon.smithy.aws.iam.traits.IamResourceTraitValidator
+software.amazon.smithy.aws.iam.traits.IamResourceTraitConflictingNameValidator
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/pattern/UriPattern.java b/smithy-model/src/main/java/software/amazon/smithy/model/pattern/UriPattern.java
index e9847ed2e..7cc90857c 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/pattern/UriPattern.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/pattern/UriPattern.java
@@ -55,12 +55,12 @@ public final class UriPattern extends SmithyPattern {
      * Parse a URI pattern string into a UriPattern.
      *
      * <p>The provided value must match the origin-form request-target
-     * grammar production in RFC 7230, section 5.3.1.
+     * grammar production in RFC 9112, section 3.2.1.
      *
      * @param uri URI pattern to parse.
      * @return Returns the parsed URI pattern.
      * @throws InvalidUriPatternException for invalid URI patterns.
-     * @see <a href="https://tools.ietf.org/html/rfc7230#section-5.3.1">RFC 7230 Section 5.3.1</a>
+     * @see <a href="https://tools.ietf.org/html/rfc9112#section-3.2.1">RFC 9112 Section 3.2.1</a>
      */
     public static UriPattern parse(String uri) {
         if (uri.endsWith("?")) {
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/TimestampFormatPlugin.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/TimestampFormatPlugin.java
index 8518356cd..24919cf17 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/TimestampFormatPlugin.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/node/TimestampFormatPlugin.java
@@ -126,7 +126,7 @@ final class TimestampFormatPlugin implements NodeValidatorPlugin {
     private String createInvalidHttpDateMessage(String dateValue) {
         return String.format(
                 "Invalid value provided for %s formatted timestamp. Expected a string value that "
-                + "matches the IMF-fixdate production of RFC 7231 section-7.1.1.1. Found: %s",
+                + "matches the IMF-fixdate production of RFC 9110 section-5.6.7. Found: %s",
                 TimestampFormatTrait.HTTP_DATE, dateValue);
     }
 
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpHeaderTraitValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpHeaderTraitValidator.java
index a438298b1..45dbff2f2 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpHeaderTraitValidator.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpHeaderTraitValidator.java
@@ -39,7 +39,7 @@ import software.amazon.smithy.utils.SetUtils;
  */
 public final class HttpHeaderTraitValidator extends AbstractValidator {
 
-    /** Gather the allowed characters for HTTP headers (tchar from RFC 7230). **/
+    /** Gather the allowed characters for HTTP headers (tchar from RFC 9110 section 5.6.2). **/
     private static final Set<Character> TCHAR = SetUtils.of(
             // "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
             '!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~',
@@ -101,7 +101,7 @@ public final class HttpHeaderTraitValidator extends AbstractValidator {
         for (int i = 0; i < header.length(); i++) {
             if (!TCHAR.contains(header.charAt(i))) {
                 return Optional.of(danger(member, trait, String.format(
-                        "`%s` is not a valid HTTP header field name according to section 3.2 of RFC 7230", header)));
+                        "`%s` is not a valid HTTP header field name according to section 5.6.2 of RFC 9110", header)));
             }
         }
 
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpMethodSemanticsValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpMethodSemanticsValidator.java
index 69f38b383..42f660a06 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpMethodSemanticsValidator.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/HttpMethodSemanticsValidator.java
@@ -35,7 +35,7 @@ import software.amazon.smithy.utils.MapUtils;
 
 /**
  * Validates that `http` traits applied to operation shapes use the most
- * semantically appropriate HTTP method according to RFC 7231.
+ * semantically appropriate HTTP method according to RFC 9110.
  */
 public final class HttpMethodSemanticsValidator extends AbstractValidator {
     /**
diff --git a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy
index abce5303e..b8a92aff7 100644
--- a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy
+++ b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy
@@ -1123,7 +1123,7 @@ enum timestampFormat {
     EPOCH_SECONDS = "epoch-seconds"
 
     /// An HTTP date as defined by the IMF-fixdate production in
-    /// RFC 7231#section-7.1.1.1 (for example, Tue, 29 Apr 2014 18:30:38 GMT).
+    /// RFC 9110#section-5.6.7 (for example, Tue, 29 Apr 2014 18:30:38 GMT).
     HTTP_DATE = "http-date"
 }
 
diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/OpenApiConfig.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/OpenApiConfig.java
index cf0db1894..90b83e14d 100644
--- a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/OpenApiConfig.java
+++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/OpenApiConfig.java
@@ -101,6 +101,7 @@ public class OpenApiConfig extends JsonSchemaConfig {
     private List<String> externalDocs = ListUtils.of(
             "Homepage", "API Reference", "User Guide", "Developer Guide", "Reference", "Guide");
     private boolean disableIntegerFormat = false;
+    private boolean syncCorsPreflightIntegration = false;
     private ErrorStatusConflictHandlingStrategy onErrorStatusConflict;
     private OpenApiVersion version = OpenApiVersion.VERSION_3_0_2;
 
@@ -355,6 +356,21 @@ public class OpenApiConfig extends JsonSchemaConfig {
     }
 
 
+    public boolean getSyncCorsPreflightIntegration() {
+        return this.syncCorsPreflightIntegration;
+    }
+
+    /**
+     * Set true to sync CORS preflight integration request templates with the other
+     * methods of the same path resource and set passthroughBehavior to "never".
+     *
+     * @param syncCorsPreflightIntegration True to match CORS preflight integration.
+     */
+    public void setSyncCorsPreflightIntegration(boolean syncCorsPreflightIntegration) {
+        this.syncCorsPreflightIntegration = syncCorsPreflightIntegration;
+    }
+
+
     public ErrorStatusConflictHandlingStrategy getOnErrorStatusConflict() {
         return onErrorStatusConflict;
     }
diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/analysis/OperationContextParamsChecker.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/analysis/OperationContextParamsChecker.java
new file mode 100644
index 000000000..9581d4c6f
--- /dev/null
+++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/analysis/OperationContextParamsChecker.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.rulesengine.analysis;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Supplier;
+import software.amazon.smithy.jmespath.JmespathExpression;
+import software.amazon.smithy.jmespath.LinterResult;
+import software.amazon.smithy.jmespath.RuntimeType;
+import software.amazon.smithy.jmespath.ast.LiteralExpression;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.knowledge.OperationIndex;
+import software.amazon.smithy.model.shapes.BigDecimalShape;
+import software.amazon.smithy.model.shapes.BigIntegerShape;
+import software.amazon.smithy.model.shapes.BlobShape;
+import software.amazon.smithy.model.shapes.BooleanShape;
+import software.amazon.smithy.model.shapes.ByteShape;
+import software.amazon.smithy.model.shapes.DocumentShape;
+import software.amazon.smithy.model.shapes.DoubleShape;
+import software.amazon.smithy.model.shapes.FloatShape;
+import software.amazon.smithy.model.shapes.IntegerShape;
+import software.amazon.smithy.model.shapes.ListShape;
+import software.amazon.smithy.model.shapes.LongShape;
+import software.amazon.smithy.model.shapes.MapShape;
+import software.amazon.smithy.model.shapes.MemberShape;
+import software.amazon.smithy.model.shapes.OperationShape;
+import software.amazon.smithy.model.shapes.ResourceShape;
+import software.amazon.smithy.model.shapes.ServiceShape;
+import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.shapes.ShapeVisitor;
+import software.amazon.smithy.model.shapes.ShortShape;
+import software.amazon.smithy.model.shapes.StringShape;
+import software.amazon.smithy.model.shapes.StructureShape;
+import software.amazon.smithy.model.shapes.TimestampShape;
+import software.amazon.smithy.model.shapes.UnionShape;
+import software.amazon.smithy.model.traits.LengthTrait;
+import software.amazon.smithy.model.traits.RangeTrait;
+import software.amazon.smithy.rulesengine.language.syntax.parameters.ParameterType;
+import software.amazon.smithy.rulesengine.traits.OperationContextParamDefinition;
+
+public final class OperationContextParamsChecker {
+
+    private OperationContextParamsChecker() {
+
+    }
+
+    public static LinterResult lint(
+            OperationContextParamDefinition paramDefinition, OperationShape operationShape, Model model) {
+        JmespathExpression path = JmespathExpression.parse(paramDefinition.getPath());
+        StructureShape input = OperationIndex.of(model).expectInputShape(operationShape);
+        return path.lint(createCurrentNodeFromShape(input, model));
+    }
+
+    public static Optional<ParameterType> inferParameterType(
+            OperationContextParamDefinition paramDefinition, OperationShape operationShape, Model model) {
+        RuntimeType runtimeType = lint(paramDefinition, operationShape, model).getReturnType();
+        switch (runtimeType) {
+            case BOOLEAN:
+                return Optional.of(ParameterType.BOOLEAN);
+            case STRING:
+                return Optional.of(ParameterType.STRING);
+            case ARRAY:
+                return Optional.of(ParameterType.STRING_ARRAY);
+            default:
+                return Optional.empty();
+        }
+    }
+
+    private static LiteralExpression createCurrentNodeFromShape(Shape shape, Model model) {
+        return shape == null
+                ? LiteralExpression.ANY
+                : new LiteralExpression(shape.accept(new ModelRuntimeTypeGenerator(model)));
+    }
+
+
+    /**
+     * This class is duplicated from
+     * smithy-waiters/src/main/java/software/amazon/smithy/waiters/ModelRuntimeTypeGenerator.java
+     *
+     * It is duplicated here to avoid taking a dependency on smithy-waiters.
+     */
+    private static final class ModelRuntimeTypeGenerator implements ShapeVisitor<Object> {
+
+        private final Model model;
+        private Set<MemberShape> visited = new HashSet<>();
+
+        ModelRuntimeTypeGenerator(Model model) {
+            this.model = model;
+        }
+
+        @Override
+        public Object blobShape(BlobShape shape) {
+            return "blob";
+        }
+
+        @Override
+        public Object booleanShape(BooleanShape shape) {
+            return true;
+        }
+
+        @Override
+        public Object byteShape(ByteShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object shortShape(ShortShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object integerShape(IntegerShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object longShape(LongShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object floatShape(FloatShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object doubleShape(DoubleShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object bigIntegerShape(BigIntegerShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object bigDecimalShape(BigDecimalShape shape) {
+            return computeRange(shape);
+        }
+
+        @Override
+        public Object documentShape(DocumentShape shape) {
+            return LiteralExpression.ANY;
+        }
+
+        @Override
+        public Object stringShape(StringShape shape) {
+            // Create a random string that does not exceed or go under the length trait.
+            int chars = computeLength(shape);
+
+            // Fill a string with "a"'s up to chars.
+            return new String(new char[chars]).replace("\0", "a");
+        }
+
+        @Override
+        public Object listShape(ListShape shape) {
+            return withCopiedVisitors(() -> {
+                int size = computeLength(shape);
+                List<Object> result = new ArrayList<>(size);
+                Object memberValue = shape.getMember().accept(this);
+                if (memberValue != null) {
+                    for (int i = 0; i < size; i++) {
+                        result.add(memberValue);
+                    }
+                }
+                return result;
+            });
+        }
+
+        // Visits members and mutates a copy of the current set of visited
+        // shapes rather than a shared set. This allows a shape to be used
+        // multiple times in the closure of a single shape without causing the
+        // reuse of the shape to always be assumed to be a recursive type.
+        private Object withCopiedVisitors(Supplier<Object> supplier) {
+            // Account for recursive shapes at the current
+            Set<MemberShape> visitedCopy = new HashSet<>(visited);
+            Object result = supplier.get();
+            visited = visitedCopy;
+            return result;
+        }
+
+        @Override
+        public Object mapShape(MapShape shape) {
+            return withCopiedVisitors(() -> {
+                int size = computeLength(shape);
+                Map<String, Object> result = new HashMap<>();
+                String key = (String) shape.getKey().accept(this);
+                Object memberValue = shape.getValue().accept(this);
+                for (int i = 0; i < size; i++) {
+                    result.put(key + i, memberValue);
+                }
+                return result;
+            });
+        }
+
+        @Override
+        public Object structureShape(StructureShape shape) {
+            return structureOrUnion(shape);
+        }
+
+        @Override
+        public Object unionShape(UnionShape shape) {
+            return structureOrUnion(shape);
+        }
+
+        private Object structureOrUnion(Shape shape) {
+            return withCopiedVisitors(() -> {
+                Map<String, Object> result = new LinkedHashMap<>();
+                for (MemberShape member : shape.members()) {
+                    Object memberValue = member.accept(this);
+                    result.put(member.getMemberName(), memberValue);
+                }
+                return result;
+            });
+        }
+
+        @Override
+        public Object memberShape(MemberShape shape) {
+            // Account for recursive shapes.
+            // A false return value means it was in the set.
+            if (!visited.add(shape)) {
+                return LiteralExpression.ANY;
+            }
+
+            return model.getShape(shape.getTarget())
+                    .map(target -> target.accept(this))
+                    // Rather than fail on broken models during waiter validation,
+                    // return an ANY to get *some* validation.
+                    .orElse(LiteralExpression.ANY);
+        }
+
+        @Override
+        public Object timestampShape(TimestampShape shape) {
+            return LiteralExpression.NUMBER;
+        }
+
+        @Override
+        public Object operationShape(OperationShape shape) {
+            throw new UnsupportedOperationException(shape.toString());
+        }
+
+        @Override
+        public Object resourceShape(ResourceShape shape) {
+            throw new UnsupportedOperationException(shape.toString());
+        }
+
+        @Override
+        public Object serviceShape(ServiceShape shape) {
+            throw new UnsupportedOperationException(shape.toString());
+        }
+
+        private int computeLength(Shape shape) {
+            // Create a random string that does not exceed or go under the length trait.
+            int chars = 2;
+
+            if (shape.hasTrait(LengthTrait.class)) {
+                LengthTrait trait = shape.expectTrait(LengthTrait.class);
+                if (trait.getMin().isPresent()) {
+                    chars = Math.max(chars, trait.getMin().get().intValue());
+                }
+                if (trait.getMax().isPresent()) {
+                    chars = Math.min(chars, trait.getMax().get().intValue());
+                }
+            }
+
+            return chars;
+        }
+
+        private double computeRange(Shape shape) {
+            // Create a random string that does not exceed or go under the range trait.
+            double i = 8;
+
+            if (shape.hasTrait(RangeTrait.class)) {
+                RangeTrait trait = shape.expectTrait(RangeTrait.class);
+                if (trait.getMin().isPresent()) {
+                    i = Math.max(i, trait.getMin().get().doubleValue());
+                }
+                if (trait.getMax().isPresent()) {
+                    i = Math.min(i, trait.getMax().get().doubleValue());
+                }
+            }
+
+            return i;
+        }
+    }
+}
diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/traits/OperationContextParamsTraitValidator.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/traits/OperationContextParamsTraitValidator.java
index c652fc1c1..e2666f1c3 100644
--- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/traits/OperationContextParamsTraitValidator.java
+++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/traits/OperationContextParamsTraitValidator.java
@@ -7,13 +7,8 @@ package software.amazon.smithy.rulesengine.traits;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import software.amazon.smithy.jmespath.ExpressionVisitor;
 import software.amazon.smithy.jmespath.JmespathException;
@@ -38,34 +33,10 @@ import software.amazon.smithy.jmespath.ast.ProjectionExpression;
 import software.amazon.smithy.jmespath.ast.SliceExpression;
 import software.amazon.smithy.jmespath.ast.Subexpression;
 import software.amazon.smithy.model.Model;
-import software.amazon.smithy.model.knowledge.OperationIndex;
-import software.amazon.smithy.model.shapes.BigDecimalShape;
-import software.amazon.smithy.model.shapes.BigIntegerShape;
-import software.amazon.smithy.model.shapes.BlobShape;
-import software.amazon.smithy.model.shapes.BooleanShape;
-import software.amazon.smithy.model.shapes.ByteShape;
-import software.amazon.smithy.model.shapes.DocumentShape;
-import software.amazon.smithy.model.shapes.DoubleShape;
-import software.amazon.smithy.model.shapes.FloatShape;
-import software.amazon.smithy.model.shapes.IntegerShape;
-import software.amazon.smithy.model.shapes.ListShape;
-import software.amazon.smithy.model.shapes.LongShape;
-import software.amazon.smithy.model.shapes.MapShape;
-import software.amazon.smithy.model.shapes.MemberShape;
 import software.amazon.smithy.model.shapes.OperationShape;
-import software.amazon.smithy.model.shapes.ResourceShape;
-import software.amazon.smithy.model.shapes.ServiceShape;
-import software.amazon.smithy.model.shapes.Shape;
-import software.amazon.smithy.model.shapes.ShapeVisitor;
-import software.amazon.smithy.model.shapes.ShortShape;
-import software.amazon.smithy.model.shapes.StringShape;
-import software.amazon.smithy.model.shapes.StructureShape;
-import software.amazon.smithy.model.shapes.TimestampShape;
-import software.amazon.smithy.model.shapes.UnionShape;
-import software.amazon.smithy.model.traits.LengthTrait;
-import software.amazon.smithy.model.traits.RangeTrait;
 import software.amazon.smithy.model.validation.AbstractValidator;
 import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.rulesengine.analysis.OperationContextParamsChecker;
 import software.amazon.smithy.utils.ListUtils;
 import software.amazon.smithy.utils.SmithyUnstableApi;
 
@@ -86,8 +57,8 @@ public final class OperationContextParamsTraitValidator extends AbstractValidato
 
                 try {
                     JmespathExpression path = JmespathExpression.parse(entry.getValue().getPath());
-                    StructureShape input = OperationIndex.of(model).expectInputShape(operationShape);
-                    LinterResult linterResult = path.lint(createCurrentNodeFromShape(input, model));
+                    LinterResult linterResult = OperationContextParamsChecker.lint(
+                            entry.getValue(), operationShape, model);
 
                     if (!linterResult.getProblems().isEmpty()) {
                         events.add(error(operationShape,
@@ -134,12 +105,6 @@ public final class OperationContextParamsTraitValidator extends AbstractValidato
         return events;
     }
 
-    private LiteralExpression createCurrentNodeFromShape(Shape shape, Model model) {
-        return shape == null
-                ? LiteralExpression.ANY
-                : new LiteralExpression(shape.accept(new ModelRuntimeTypeGenerator(model)));
-    }
-
     private static final class UnsupportedJmesPathVisitor implements ExpressionVisitor<List<String>> {
 
         @Override
@@ -245,215 +210,4 @@ public final class OperationContextParamsTraitValidator extends AbstractValidato
             return Collections.unmodifiableList(unsupported);
         }
     }
-
-    /**
-     * This class is duplicated from
-     * smithy-waiters/src/main/java/software/amazon/smithy/waiters/ModelRuntimeTypeGenerator.java
-     *
-     * It is duplicated here to avoid taking a dependency on smithy-waiters.
-     */
-    private static final class ModelRuntimeTypeGenerator implements ShapeVisitor<Object> {
-
-        private final Model model;
-        private Set<MemberShape> visited = new HashSet<>();
-
-        ModelRuntimeTypeGenerator(Model model) {
-            this.model = model;
-        }
-
-        @Override
-        public Object blobShape(BlobShape shape) {
-            return "blob";
-        }
-
-        @Override
-        public Object booleanShape(BooleanShape shape) {
-            return true;
-        }
-
-        @Override
-        public Object byteShape(ByteShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object shortShape(ShortShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object integerShape(IntegerShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object longShape(LongShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object floatShape(FloatShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object doubleShape(DoubleShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object bigIntegerShape(BigIntegerShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object bigDecimalShape(BigDecimalShape shape) {
-            return computeRange(shape);
-        }
-
-        @Override
-        public Object documentShape(DocumentShape shape) {
-            return LiteralExpression.ANY;
-        }
-
-        @Override
-        public Object stringShape(StringShape shape) {
-            // Create a random string that does not exceed or go under the length trait.
-            int chars = computeLength(shape);
-
-            // Fill a string with "a"'s up to chars.
-            return new String(new char[chars]).replace("\0", "a");
-        }
-
-        @Override
-        public Object listShape(ListShape shape) {
-            return withCopiedVisitors(() -> {
-                int size = computeLength(shape);
-                List<Object> result = new ArrayList<>(size);
-                Object memberValue = shape.getMember().accept(this);
-                if (memberValue != null) {
-                    for (int i = 0; i < size; i++) {
-                        result.add(memberValue);
-                    }
-                }
-                return result;
-            });
-        }
-
-        // Visits members and mutates a copy of the current set of visited
-        // shapes rather than a shared set. This allows a shape to be used
-        // multiple times in the closure of a single shape without causing the
-        // reuse of the shape to always be assumed to be a recursive type.
-        private Object withCopiedVisitors(Supplier<Object> supplier) {
-            // Account for recursive shapes at the current
-            Set<MemberShape> visitedCopy = new HashSet<>(visited);
-            Object result = supplier.get();
-            visited = visitedCopy;
-            return result;
-        }
-
-        @Override
-        public Object mapShape(MapShape shape) {
-            return withCopiedVisitors(() -> {
-                int size = computeLength(shape);
-                Map<String, Object> result = new HashMap<>();
-                String key = (String) shape.getKey().accept(this);
-                Object memberValue = shape.getValue().accept(this);
-                for (int i = 0; i < size; i++) {
-                    result.put(key + i, memberValue);
-                }
-                return result;
-            });
-        }
-
-        @Override
-        public Object structureShape(StructureShape shape) {
-            return structureOrUnion(shape);
-        }
-
-        @Override
-        public Object unionShape(UnionShape shape) {
-            return structureOrUnion(shape);
-        }
-
-        private Object structureOrUnion(Shape shape) {
-            return withCopiedVisitors(() -> {
-                Map<String, Object> result = new LinkedHashMap<>();
-                for (MemberShape member : shape.members()) {
-                    Object memberValue = member.accept(this);
-                    result.put(member.getMemberName(), memberValue);
-                }
-                return result;
-            });
-        }
-
-        @Override
-        public Object memberShape(MemberShape shape) {
-            // Account for recursive shapes.
-            // A false return value means it was in the set.
-            if (!visited.add(shape)) {
-                return LiteralExpression.ANY;
-            }
-
-            return model.getShape(shape.getTarget())
-                    .map(target -> target.accept(this))
-                    // Rather than fail on broken models during waiter validation,
-                    // return an ANY to get *some* validation.
-                    .orElse(LiteralExpression.ANY);
-        }
-
-        @Override
-        public Object timestampShape(TimestampShape shape) {
-            return LiteralExpression.NUMBER;
-        }
-
-        @Override
-        public Object operationShape(OperationShape shape) {
-            throw new UnsupportedOperationException(shape.toString());
-        }
-
-        @Override
-        public Object resourceShape(ResourceShape shape) {
-            throw new UnsupportedOperationException(shape.toString());
-        }
-
-        @Override
-        public Object serviceShape(ServiceShape shape) {
-            throw new UnsupportedOperationException(shape.toString());
-        }
-
-        private int computeLength(Shape shape) {
-            // Create a random string that does not exceed or go under the length trait.
-            int chars = 2;
-
-            if (shape.hasTrait(LengthTrait.class)) {
-                LengthTrait trait = shape.expectTrait(LengthTrait.class);
-                if (trait.getMin().isPresent()) {
-                    chars = Math.max(chars, trait.getMin().get().intValue());
-                }
-                if (trait.getMax().isPresent()) {
-                    chars = Math.min(chars, trait.getMax().get().intValue());
-                }
-            }
-
-            return chars;
-        }
-
-        private double computeRange(Shape shape) {
-            // Create a random string that does not exceed or go under the range trait.
-            double i = 8;
-
-            if (shape.hasTrait(RangeTrait.class)) {
-                RangeTrait trait = shape.expectTrait(RangeTrait.class);
-                if (trait.getMin().isPresent()) {
-                    i = Math.max(i, trait.getMin().get().doubleValue());
-                }
-                if (trait.getMax().isPresent()) {
-                    i = Math.min(i, trait.getMax().get().doubleValue());
-                }
-            }
-
-            return i;
-        }
-    }
 }
diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/validators/RuleSetParameterValidator.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/validators/RuleSetParameterValidator.java
index a08d3bc0b..27f6d9f45 100644
--- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/validators/RuleSetParameterValidator.java
+++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/validators/RuleSetParameterValidator.java
@@ -10,6 +10,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import software.amazon.smithy.model.FromSourceLocation;
 import software.amazon.smithy.model.Model;
@@ -22,6 +23,7 @@ import software.amazon.smithy.model.shapes.Shape;
 import software.amazon.smithy.model.shapes.StructureShape;
 import software.amazon.smithy.model.validation.AbstractValidator;
 import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.rulesengine.analysis.OperationContextParamsChecker;
 import software.amazon.smithy.rulesengine.language.EndpointRuleSet;
 import software.amazon.smithy.rulesengine.language.evaluation.value.Value;
 import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter;
@@ -34,6 +36,7 @@ import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
 import software.amazon.smithy.rulesengine.traits.EndpointTestCase;
 import software.amazon.smithy.rulesengine.traits.EndpointTestOperationInput;
 import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait;
+import software.amazon.smithy.rulesengine.traits.OperationContextParamsTrait;
 import software.amazon.smithy.rulesengine.traits.StaticContextParamDefinition;
 import software.amazon.smithy.rulesengine.traits.StaticContextParamsTrait;
 import software.amazon.smithy.utils.ListUtils;
@@ -80,7 +83,7 @@ public final class RuleSetParameterValidator extends AbstractValidator {
 
         if (serviceShape.hasTrait(ClientContextParamsTrait.class)) {
             ClientContextParamsTrait trait = serviceShape.expectTrait(ClientContextParamsTrait.class);
-            for (Map.Entry<String, ClientContextParamDefinition> entry : trait.getParameters().entrySet())  {
+            for (Map.Entry<String, ClientContextParamDefinition> entry : trait.getParameters().entrySet()) {
                 endpointParams.put(entry.getKey(), Parameter.builder()
                         .name(entry.getKey())
                         .type(ParameterType.fromShapeType(entry.getValue().getType()))
@@ -108,6 +111,26 @@ public final class RuleSetParameterValidator extends AbstractValidator {
                 }
             }
 
+            if (operationShape.hasTrait(OperationContextParamsTrait.ID)) {
+                OperationContextParamsTrait trait = operationShape.expectTrait(OperationContextParamsTrait.class);
+                trait.getParameters().forEach((name, p) -> {
+                    Optional<ParameterType> maybeType = OperationContextParamsChecker
+                            .inferParameterType(p, operationShape, model);
+                    maybeType.ifPresent(parameterType -> {
+                        if (endpointParams.containsKey(name) && endpointParams.get(name).getType() != parameterType) {
+                            errors.add(parameterError(operationShape, trait,
+                                    "OperationContextParams.InconsistentType",
+                                    String.format("Inconsistent type for `%s` parameter", name)));
+                        } else {
+                            endpointParams.put(name, Parameter.builder()
+                                    .name(name)
+                                    .type(parameterType)
+                                    .build());
+                        }
+                    });
+                });
+            }
+
             StructureShape input = model.expectShape(operationShape.getInputShape(), StructureShape.class);
             for (MemberShape memberShape : input.members()) {
                 if (memberShape.hasTrait(ContextParamTrait.class)) {
@@ -200,7 +223,7 @@ public final class RuleSetParameterValidator extends AbstractValidator {
             // All test parameter types from corresponding ruleset parameters must match in all test cases.
             if (!testSuiteHasParam) {
                 errors.add(danger(serviceShape, parameter,
-                                String.format("Parameter `%s` is never used in an `EndpointTests` test case", name))
+                        String.format("Parameter `%s` is never used in an `EndpointTests` test case", name))
                         .toBuilder()
                         .id(getName() + ".TestCase.Unused").build());
             } else {
@@ -208,7 +231,7 @@ public final class RuleSetParameterValidator extends AbstractValidator {
                     if (testParam.getType() != parameter.getType()) {
                         errors.add(parameterError(serviceShape, testParam, "TestCase.TypeMismatch",
                                 String.format("Type mismatch for parameter `%s`, `%s` expected",
-                                testParam.getName().toString(), parameter.getType())));
+                                        testParam.getName().toString(), parameter.getType())));
                     }
                 }
             }
@@ -302,11 +325,11 @@ public final class RuleSetParameterValidator extends AbstractValidator {
     private Parameter buildParameter(String name, Node node) {
         Value value = Value.fromNode(node);
         return Parameter.builder()
-                       .sourceLocation(value)
-                       .name(name)
-                       .value(value)
-                       .type(ParameterType.fromType(value.getType()))
-                       .build();
+                .sourceLocation(value)
+                .name(name)
+                .value(value)
+                .type(ParameterType.fromType(value.getType()))
+                .build();
     }
 
     private List<Parameter> merge(List<Parameter> previousList, List<Parameter> newList) {
diff --git a/smithy-utils/src/main/java/software/amazon/smithy/utils/MediaType.java b/smithy-utils/src/main/java/software/amazon/smithy/utils/MediaType.java
index 96492e025..66ff16794 100644
--- a/smithy-utils/src/main/java/software/amazon/smithy/utils/MediaType.java
+++ b/smithy-utils/src/main/java/software/amazon/smithy/utils/MediaType.java
@@ -26,7 +26,7 @@ import java.util.Set;
 
 /**
  * Implements a simple media type parser based on the Content-Type grammar defined in
- * <a href="https://tools.ietf.org/html/rfc7231#section-3.1.1.1">RFC 7231</a>.
+ * <a href="https://tools.ietf.org/html/rfc9110#section-8.3">RFC 9110</a>.
  *
  * <p>The type, subtype, and parameter names are all canonicalized to
  * lowercase strings.
@@ -154,7 +154,7 @@ public final class MediaType {
     }
 
     private static final class Parser extends SimpleParser {
-        // See https://tools.ietf.org/html/rfc7230#section-3.2.6
+        // See https://tools.ietf.org/html/rfc9110#section-5.6.2
         // token          = 1*tchar
         // tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
         //                / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
@@ -198,7 +198,7 @@ public final class MediaType {
         }
 
         private void parse() {
-            // From: https://tools.ietf.org/html/rfc7231#section-3.1.1.1
+            // From: https://tools.ietf.org/html/rfc9110#section-8.3.1
             // The type, subtype, and parameter name tokens are case-insensitive.
             //     media-type = type "/" subtype *( OWS ";" OWS parameter )
             //     type       = token.
@@ -240,7 +240,7 @@ public final class MediaType {
             return sliceFrom(start);
         }
 
-        // See https://tools.ietf.org/html/rfc7230#section-3.2.6
+        // See https://tools.ietf.org/html/rfc9110#section-5.6.4
         // quoted-string  = DQUOTE *( qdtext / quoted-pair ) DQUOTE
         // qdtext         = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
         // obs-text       = %x80-FF


Test patch:
diff --git a/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/CorsTest.java b/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/CorsTest.java
index dd4caa027..1cd507ecd 100644
--- a/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/CorsTest.java
+++ b/smithy-aws-apigateway-openapi/src/test/java/software/amazon/smithy/aws/apigateway/openapi/CorsTest.java
@@ -112,4 +112,37 @@ public class CorsTest {
 
         Node.assertEquals(result, expectedNode);
     }
+
+    @Test
+    public void withPreflightIntegrationSync() {
+        Model model = Model.assembler(getClass().getClassLoader())
+                .discoverModels(getClass().getClassLoader())
+                .addImport(getClass().getResource("cors-with-multi-request-templates.json"))
+                .assemble()
+                .unwrap();
+        OpenApiConfig config = new OpenApiConfig();
+        config.setService(ShapeId.from("example.smithy#MyService"));
+        config.setSyncCorsPreflightIntegration(true);
+        ObjectNode result = OpenApiConverter.create().config(config).convertToNode(model);
+        Node expectedNode = Node.parse(IoUtils.toUtf8String(
+                getClass().getResourceAsStream("cors-with-preflight-sync.openapi.json")));
+
+        Node.assertEquals(result, expectedNode);
+    }
+
+    @Test
+    public void withoutPreflightIntegrationSync() {
+        Model model = Model.assembler(getClass().getClassLoader())
+                .discoverModels(getClass().getClassLoader())
+                .addImport(getClass().getResource("cors-with-multi-request-templates.json"))
+                .assemble()
+                .unwrap();
+        OpenApiConfig config = new OpenApiConfig();
+        config.setService(ShapeId.from("example.smithy#MyService"));
+        ObjectNode result = OpenApiConverter.create().config(config).convertToNode(model);
+        Node expectedNode = Node.parse(IoUtils.toUtf8String(
+                getClass().getResourceAsStream("cors-without-preflight-sync.openapi.json")));
+
+        Node.assertEquals(result, expectedNode);
+    }
 }
diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-multi-request-templates.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-multi-request-templates.json
new file mode 100644
index 000000000..714d3df7d
--- /dev/null
+++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-multi-request-templates.json
@@ -0,0 +1,102 @@
+{
+    "smithy": "2.0",
+    "shapes": {
+        "example.smithy#MyService": {
+            "type": "service",
+            "version": "2006-03-01",
+            "operations": [
+                {
+                    "target": "example.smithy#MockPut"
+                },
+                {
+                    "target": "example.smithy#MockGet"
+                }
+            ],
+            "traits": {
+                "aws.protocols#restJson1": {},
+                "aws.auth#sigv4": {
+                    "name": "myservice"
+                },
+                "smithy.api#cors": {
+                    "origin": "https://www.example.com",
+                    "maxAge": 86400,
+                    "additionalAllowedHeaders": [
+                        "X-Service-Input-Metadata"
+                    ],
+                    "additionalExposedHeaders": [
+                        "X-Service-Output-Metadata"
+                    ]
+                }
+            }
+        },
+        "example.smithy#MockGet": {
+            "type": "operation",
+            "output": {
+                "target": "example.smithy#MockOutput"
+            },
+            "traits": {
+                "aws.apigateway#mockIntegration": {
+                    "passThroughBehavior": "never",
+                    "requestTemplates": {
+                        "application/json": "{\"statusCode\": 200}",
+                        "application/x-www-form-urlencoded": "{\"statusCode\": 200}"
+                    },
+                    "responses": {
+                        "default": {
+                            "statusCode": "200",
+                            "responseTemplates": {
+                                "application/json": "{\"extendedRequestId\": \"$context.extendedRequestId\"}"
+                            }
+                        }
+                    }
+                },
+                "smithy.api#http": {
+                    "code": 200,
+                    "method": "GET",
+                    "uri": "/mock"
+                },
+                "smithy.api#readonly": {}
+            }
+        },
+        "example.smithy#MockPut": {
+            "type": "operation",
+            "output": {
+                "target": "example.smithy#MockOutput"
+            },
+            "traits": {
+                "aws.apigateway#mockIntegration": {
+                    "passThroughBehavior": "never",
+                    "requestTemplates": {
+                        "text/plain": "{\"statusCode\": 200}",
+                        "application/xml": "{\"statusCode\": 200}"
+                    },
+                    "responses": {
+                        "default": {
+                            "statusCode": "200",
+                            "responseTemplates": {
+                                "application/json": "{\"extendedRequestId\": \"$context.extendedRequestId\"}"
+                            }
+                        }
+                    }
+                },
+                "smithy.api#http": {
+                    "code": 201,
+                    "method": "PUT",
+                    "uri": "/mock"
+                },
+                "smithy.api#idempotent": {}
+            }
+        },
+        "example.smithy#MockOutput": {
+            "type": "structure",
+            "members": {
+                "extendedRequestId": {
+                    "target": "smithy.api#String",
+                    "traits": {
+                        "smithy.api#required": {}
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-preflight-sync.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-preflight-sync.openapi.json
new file mode 100644
index 000000000..5f3b8154b
--- /dev/null
+++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-with-preflight-sync.openapi.json
@@ -0,0 +1,222 @@
+{
+  "openapi": "3.0.2",
+  "info": {
+    "title": "MyService",
+    "version": "2006-03-01"
+  },
+  "paths": {
+    "/mock": {
+      "get": {
+        "operationId": "MockGet",
+        "responses": {
+          "200": {
+            "description": "MockGet 200 response",
+            "headers": {
+              "Access-Control-Allow-Origin": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Expose-Headers": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            },
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/MockGetResponseContent"
+                }
+              }
+            }
+          }
+        },
+        "x-amazon-apigateway-integration": {
+          "requestTemplates": {
+            "application/json": "{\"statusCode\": 200}",
+            "application/x-www-form-urlencoded": "{\"statusCode\": 200}"
+          },
+          "responses": {
+            "default": {
+              "statusCode": "200",
+              "responseTemplates": {
+                "application/json": "{\"extendedRequestId\": \"$context.extendedRequestId\"}"
+              },
+              "responseParameters": {
+                "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+                "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'"
+              }
+            }
+          },
+          "type": "mock",
+          "passthroughBehavior": "never"
+        }
+      },
+      "options": {
+        "description": "Handles CORS-preflight requests",
+        "operationId": "CorsMock",
+        "responses": {
+          "200": {
+            "description": "Canned response for CORS-preflight requests",
+            "headers": {
+              "Access-Control-Allow-Headers": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Allow-Methods": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Allow-Origin": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Max-Age": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            }
+          }
+        },
+        "security": [],
+        "tags": [
+          "CORS"
+        ],
+        "x-amazon-apigateway-integration": {
+          "contentHandling": "CONVERT_TO_TEXT",
+          "requestTemplates": {
+            "application/xml": "{\"statusCode\":200}",
+            "application/x-www-form-urlencoded": "{\"statusCode\":200}",
+            "application/json": "{\"statusCode\":200}",
+            "text/plain": "{\"statusCode\":200}"
+          },
+          "responses": {
+            "default": {
+              "responseParameters": {
+                "method.response.header.Access-Control-Max-Age": "'86400'",
+                "method.response.header.Access-Control-Allow-Headers": "'Amz-Sdk-Invocation-Id,Amz-Sdk-Request,Authorization,Date,Host,X-Amz-Content-Sha256,X-Amz-Date,X-Amz-Security-Token,X-Amz-Target,X-Amz-User-Agent,X-Amzn-Trace-Id,X-Service-Input-Metadata'",
+                "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+                "method.response.header.Access-Control-Allow-Methods": "'GET,PUT'"
+              },
+              "statusCode": "200"
+            }
+          },
+          "type": "mock",
+          "passthroughBehavior": "never"
+        }
+      },
+      "put": {
+        "operationId": "MockPut",
+        "responses": {
+          "201": {
+            "description": "MockPut 201 response",
+            "headers": {
+              "Access-Control-Allow-Origin": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Expose-Headers": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            },
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/MockPutResponseContent"
+                }
+              }
+            }
+          }
+        },
+        "x-amazon-apigateway-integration": {
+          "requestTemplates": {
+            "text/plain": "{\"statusCode\": 200}",
+            "application/xml": "{\"statusCode\": 200}"
+          },
+          "responses": {
+            "default": {
+              "statusCode": "200",
+              "responseTemplates": {
+                "application/json": "{\"extendedRequestId\": \"$context.extendedRequestId\"}"
+              },
+              "responseParameters": {
+                "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+                "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'"
+              }
+            }
+          },
+          "type": "mock",
+          "passthroughBehavior": "never"
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "MockGetResponseContent": {
+        "type": "object",
+        "properties": {
+          "extendedRequestId": {
+            "type": "string"
+          }
+        },
+        "required": [
+          "extendedRequestId"
+        ]
+      },
+      "MockPutResponseContent": {
+        "type": "object",
+        "properties": {
+          "extendedRequestId": {
+            "type": "string"
+          }
+        },
+        "required": [
+          "extendedRequestId"
+        ]
+      }
+    },
+    "securitySchemes": {
+      "aws.auth.sigv4": {
+        "type": "apiKey",
+        "description": "AWS Signature Version 4 authentication",
+        "name": "Authorization",
+        "in": "header",
+        "x-amazon-apigateway-authtype": "awsSigv4"
+      }
+    }
+  },
+  "security": [
+    {
+      "aws.auth.sigv4": [ ]
+    }
+  ],
+  "x-amazon-apigateway-gateway-responses": {
+    "DEFAULT_4XX": {
+      "responseTemplates": {
+        "application/json": "{\"message\":$context.error.messageString}"
+      },
+      "responseParameters": {
+        "gatewayresponse.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+        "gatewayresponse.header.Access-Control-Expose-Headers": "'X-Service-Output-Metadata'"
+      }
+    },
+    "DEFAULT_5XX": {
+      "responseTemplates": {
+        "application/json": "{\"message\":$context.error.messageString}"
+      },
+      "responseParameters": {
+        "gatewayresponse.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+        "gatewayresponse.header.Access-Control-Expose-Headers": "'X-Service-Output-Metadata'"
+      }
+    }
+  }
+}
diff --git a/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-without-preflight-sync.openapi.json b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-without-preflight-sync.openapi.json
new file mode 100644
index 000000000..2ce6e10fa
--- /dev/null
+++ b/smithy-aws-apigateway-openapi/src/test/resources/software/amazon/smithy/aws/apigateway/openapi/cors-without-preflight-sync.openapi.json
@@ -0,0 +1,219 @@
+{
+  "openapi": "3.0.2",
+  "info": {
+    "title": "MyService",
+    "version": "2006-03-01"
+  },
+  "paths": {
+    "/mock": {
+      "get": {
+        "operationId": "MockGet",
+        "responses": {
+          "200": {
+            "description": "MockGet 200 response",
+            "headers": {
+              "Access-Control-Allow-Origin": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Expose-Headers": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            },
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/MockGetResponseContent"
+                }
+              }
+            }
+          }
+        },
+        "x-amazon-apigateway-integration": {
+          "requestTemplates": {
+            "application/json": "{\"statusCode\": 200}",
+            "application/x-www-form-urlencoded": "{\"statusCode\": 200}"
+          },
+          "responses": {
+            "default": {
+              "statusCode": "200",
+              "responseTemplates": {
+                "application/json": "{\"extendedRequestId\": \"$context.extendedRequestId\"}"
+              },
+              "responseParameters": {
+                "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+                "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'"
+              }
+            }
+          },
+          "type": "mock",
+          "passthroughBehavior": "never"
+        }
+      },
+      "options": {
+        "description": "Handles CORS-preflight requests",
+        "operationId": "CorsMock",
+        "responses": {
+          "200": {
+            "description": "Canned response for CORS-preflight requests",
+            "headers": {
+              "Access-Control-Allow-Headers": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Allow-Methods": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Allow-Origin": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Max-Age": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            }
+          }
+        },
+        "security": [],
+        "tags": [
+          "CORS"
+        ],
+        "x-amazon-apigateway-integration": {
+          "contentHandling": "CONVERT_TO_TEXT",
+          "requestTemplates": {
+            "application/json": "{\"statusCode\":200}"
+          },
+          "responses": {
+            "default": {
+              "responseParameters": {
+                "method.response.header.Access-Control-Max-Age": "'86400'",
+                "method.response.header.Access-Control-Allow-Headers": "'Amz-Sdk-Invocation-Id,Amz-Sdk-Request,Authorization,Date,Host,X-Amz-Content-Sha256,X-Amz-Date,X-Amz-Security-Token,X-Amz-Target,X-Amz-User-Agent,X-Amzn-Trace-Id,X-Service-Input-Metadata'",
+                "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+                "method.response.header.Access-Control-Allow-Methods": "'GET,PUT'"
+              },
+              "statusCode": "200"
+            }
+          },
+          "type": "mock",
+          "passthroughBehavior": "when_no_match"
+        }
+      },
+      "put": {
+        "operationId": "MockPut",
+        "responses": {
+          "201": {
+            "description": "MockPut 201 response",
+            "headers": {
+              "Access-Control-Allow-Origin": {
+                "schema": {
+                  "type": "string"
+                }
+              },
+              "Access-Control-Expose-Headers": {
+                "schema": {
+                  "type": "string"
+                }
+              }
+            },
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/MockPutResponseContent"
+                }
+              }
+            }
+          }
+        },
+        "x-amazon-apigateway-integration": {
+          "requestTemplates": {
+            "text/plain": "{\"statusCode\": 200}",
+            "application/xml": "{\"statusCode\": 200}"
+          },
+          "responses": {
+            "default": {
+              "statusCode": "200",
+              "responseTemplates": {
+                "application/json": "{\"extendedRequestId\": \"$context.extendedRequestId\"}"
+              },
+              "responseParameters": {
+                "method.response.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+                "method.response.header.Access-Control-Expose-Headers": "'Content-Length,Content-Type,X-Amzn-Errortype,X-Amzn-Requestid,X-Service-Output-Metadata'"
+              }
+            }
+          },
+          "type": "mock",
+          "passthroughBehavior": "never"
+        }
+      }
+    }
+  },
+  "components": {
+    "schemas": {
+      "MockGetResponseContent": {
+        "type": "object",
+        "properties": {
+          "extendedRequestId": {
+            "type": "string"
+          }
+        },
+        "required": [
+          "extendedRequestId"
+        ]
+      },
+      "MockPutResponseContent": {
+        "type": "object",
+        "properties": {
+          "extendedRequestId": {
+            "type": "string"
+          }
+        },
+        "required": [
+          "extendedRequestId"
+        ]
+      }
+    },
+    "securitySchemes": {
+      "aws.auth.sigv4": {
+        "type": "apiKey",
+        "description": "AWS Signature Version 4 authentication",
+        "name": "Authorization",
+        "in": "header",
+        "x-amazon-apigateway-authtype": "awsSigv4"
+      }
+    }
+  },
+  "security": [
+    {
+      "aws.auth.sigv4": [ ]
+    }
+  ],
+  "x-amazon-apigateway-gateway-responses": {
+    "DEFAULT_4XX": {
+      "responseTemplates": {
+        "application/json": "{\"message\":$context.error.messageString}"
+      },
+      "responseParameters": {
+        "gatewayresponse.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+        "gatewayresponse.header.Access-Control-Expose-Headers": "'X-Service-Output-Metadata'"
+      }
+    },
+    "DEFAULT_5XX": {
+      "responseTemplates": {
+        "application/json": "{\"message\":$context.error.messageString}"
+      },
+      "responseParameters": {
+        "gatewayresponse.header.Access-Control-Allow-Origin": "'https://www.example.com'",
+        "gatewayresponse.header.Access-Control-Expose-Headers": "'X-Service-Output-Metadata'"
+      }
+    }
+  }
+}
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/duplicate-iam-resources-name.errors b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/duplicate-iam-resources-name.errors
new file mode 100644
index 000000000..976e0c872
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/duplicate-iam-resources-name.errors
@@ -0,0 +1,1 @@
+[ERROR] smithy.example#MyService: Multiple IAM resources defined with the same IAM resource name is not allowed in a service closure, but found multiple resources named `Beer` in the service `smithy.example#MyService`: [`smithy.example#BadIamResourceName1`, `smithy.example#BadIamResourceName2`, `smithy.example#Beer`] | IamResourceTraitConflictingName
diff --git a/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/duplicate-iam-resources-name.smithy b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/duplicate-iam-resources-name.smithy
new file mode 100644
index 000000000..9833e42db
--- /dev/null
+++ b/smithy-aws-iam-traits/src/test/resources/software/amazon/smithy/aws/iam/traits/errorfiles/iam-resources/duplicate-iam-resources-name.smithy
@@ -0,0 +1,39 @@
+$version: "2"
+namespace smithy.example
+
+use aws.api#arn
+
+@aws.api#service(sdkId: "My")
+@aws.iam#defineConditionKeys("foo:baz": {type: "String", documentation: "Foo baz"})
+service MyService {
+    version: "2019-02-20",
+    resources: [
+        BadIamResourceName1,
+        BadIamResourceName2,
+        Beer,
+        ShouldNotThrowAnErrorFirst,
+        ShouldNotThrowAnErrorSecond
+    ]
+}
+
+resource Beer {}
+
+@aws.iam#iamResource(name: "Beer")
+resource BadIamResourceName1 {}
+
+@aws.iam#iamResource(name: "Beer")
+resource BadIamResourceName2 {}
+
+@aws.iam#iamResource(name: "shouldNotThrowErrorSecond")
+resource ShouldNotThrowAnErrorFirst {
+    identifiers: {
+        beerId: String
+    }
+}
+
+@aws.iam#iamResource(name: "shouldNotThrowErrorFirst")
+resource ShouldNotThrowAnErrorSecond {
+    identifiers: {
+        arn: String
+    }
+}
diff --git a/smithy-aws-protocol-tests/model/restJson1/defaults.smithy b/smithy-aws-protocol-tests/model/restJson1/defaults.smithy
new file mode 100644
index 000000000..f14363293
--- /dev/null
+++ b/smithy-aws-protocol-tests/model/restJson1/defaults.smithy
@@ -0,0 +1,460 @@
+$version: "2.0"
+
+namespace aws.protocoltests.restjson
+
+use aws.protocols#restJson1
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+
+apply OperationWithDefaults @httpRequestTests([
+    {
+        id: "RestJsonClientPopulatesDefaultValuesInInput"
+        documentation: "Client populates default values in input."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithDefaults"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "defaults": {
+                    "defaultString": "hi",
+                    "defaultBoolean": true,
+                    "defaultList": [],
+                    "defaultDocumentMap": {},
+                    "defaultDocumentString": "hi",
+                    "defaultDocumentBoolean": true,
+                    "defaultDocumentList": [],
+                    "defaultTimestamp": 0,
+                    "defaultBlob": "YWJj",
+                    "defaultByte": 1,
+                    "defaultShort": 1,
+                    "defaultInteger": 10,
+                    "defaultLong": 100,
+                    "defaultFloat": 1.0,
+                    "defaultDouble": 1.0,
+                    "defaultMap": {},
+                    "defaultEnum": "FOO",
+                    "defaultIntEnum": 1,
+                    "emptyString": "",
+                    "falseBoolean": false,
+                    "emptyBlob": "",
+                    "zeroByte": 0,
+                    "zeroShort": 0,
+                    "zeroInteger": 0,
+                    "zeroLong": 0,
+                    "zeroFloat": 0.0,
+                    "zeroDouble": 0.0
+                }
+            }"""
+        params: {
+            defaults: {}
+        }
+    }
+    {
+        id: "RestJsonClientSkipsTopLevelDefaultValuesInInput"
+        documentation: "Client skips top level default values in input."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithDefaults"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+            }"""
+        params: {
+        }
+    }
+    {
+        id: "RestJsonClientUsesExplicitlyProvidedMemberValuesOverDefaults"
+        documentation: "Client uses explicitly provided member values over defaults"
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithDefaults"
+        headers: {"Content-Type": "application/json"}
+        params: {
+            defaults: {
+                defaultString: "bye"
+                defaultBoolean: true
+                defaultList: ["a"]
+                defaultDocumentMap: {name: "Jack"}
+                defaultDocumentString: "bye"
+                defaultDocumentBoolean: true
+                defaultDocumentList: ["b"]
+                defaultNullDocument: "notNull"
+                defaultTimestamp: 1
+                defaultBlob: "hi"
+                defaultByte: 2
+                defaultShort: 2
+                defaultInteger: 20
+                defaultLong: 200
+                defaultFloat: 2.0
+                defaultDouble: 2.0
+                defaultMap: {name: "Jack"}
+                defaultEnum: "BAR"
+                defaultIntEnum: 2
+                emptyString: "foo"
+                falseBoolean: true
+                emptyBlob: "hi"
+                zeroByte: 1
+                zeroShort: 1
+                zeroInteger: 1
+                zeroLong: 1
+                zeroFloat: 1.0
+                zeroDouble: 1.0
+            }
+        }
+        body: """
+            {
+                "defaults": {
+                    "defaultString": "bye",
+                    "defaultBoolean": true,
+                    "defaultList": ["a"],
+                    "defaultDocumentMap": {"name": "Jack"},
+                    "defaultDocumentString": "bye",
+                    "defaultDocumentBoolean": true,
+                    "defaultDocumentList": ["b"],
+                    "defaultNullDocument": "notNull",
+                    "defaultTimestamp": 1,
+                    "defaultBlob": "aGk=",
+                    "defaultByte": 2,
+                    "defaultShort": 2,
+                    "defaultInteger": 20,
+                    "defaultLong": 200,
+                    "defaultFloat": 2.0,
+                    "defaultDouble": 2.0,
+                    "defaultMap": {"name": "Jack"},
+                    "defaultEnum": "BAR",
+                    "defaultIntEnum": 2,
+                    "emptyString": "foo",
+                    "falseBoolean": true,
+                    "emptyBlob": "aGk=",
+                    "zeroByte": 1,
+                    "zeroShort": 1,
+                    "zeroInteger": 1,
+                    "zeroLong": 1,
+                    "zeroFloat": 1.0,
+                    "zeroDouble": 1.0
+                }
+            }"""
+    }
+    {
+        id: "RestJsonServerPopulatesDefaultsWhenMissingInRequestBody"
+        documentation: "Server populates default values when missing in request body."
+        appliesTo: "server"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithDefaults"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+            "defaults": {}
+            }"""
+        params: {
+            defaults: {
+                defaultString: "hi"
+                defaultBoolean: true
+                defaultList: []
+                defaultDocumentMap: {}
+                defaultDocumentString: "hi"
+                defaultDocumentBoolean: true
+                defaultDocumentList: []
+                defaultTimestamp: 0
+                defaultBlob: "abc"
+                defaultByte: 1
+                defaultShort: 1
+                defaultInteger: 10
+                defaultLong: 100
+                defaultFloat: 1.0
+                defaultDouble: 1.0
+                defaultMap: {}
+                defaultEnum: "FOO"
+                defaultIntEnum: 1
+                emptyString: ""
+                falseBoolean: false
+                emptyBlob: ""
+                zeroByte: 0
+                zeroShort: 0
+                zeroInteger: 0
+                zeroLong: 0
+                zeroFloat: 0.0
+                zeroDouble: 0.0
+            },
+            topLevelDefault: "hi"
+            otherTopLevelDefault: 0
+        }
+    }
+    {
+        id: "RestJsonClientUsesExplicitlyProvidedValuesInTopLevel"
+        documentation: "Any time a value is provided for a member in the top level of input, it is used, regardless of if its the default."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithDefaults"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "topLevelDefault": "hi",
+                "otherTopLevelDefault": 0
+            }"""
+        params: {
+            topLevelDefault: "hi"
+            otherTopLevelDefault: 0
+        }
+    }
+    {
+        id: "RestJsonClientIgnoresNonTopLevelDefaultsOnMembersWithClientOptional"
+        documentation: "Typically, non top-level members would have defaults filled in, but if they have the clientOptional trait, the defaults should be ignored."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithDefaults"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "clientOptionalDefaults": {}
+            }"""
+        params: {
+            clientOptionalDefaults: {}
+        }
+    }
+])
+
+apply OperationWithDefaults @httpResponseTests([
+    {
+        id: "RestJsonClientPopulatesDefaultsValuesWhenMissingInResponse"
+        documentation: "Client populates default values when missing in response."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        code: 200
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/json"}
+        body: "{}"
+        params: {
+            defaultString: "hi"
+            defaultBoolean: true
+            defaultList: []
+            defaultDocumentMap: {}
+            defaultDocumentString: "hi"
+            defaultDocumentBoolean: true
+            defaultDocumentList: []
+            defaultTimestamp: 0
+            defaultBlob: "abc"
+            defaultByte: 1
+            defaultShort: 1
+            defaultInteger: 10
+            defaultLong: 100
+            defaultFloat: 1.0
+            defaultDouble: 1.0
+            defaultMap: {}
+            defaultEnum: "FOO"
+            defaultIntEnum: 1
+            emptyString: ""
+            falseBoolean: false
+            emptyBlob: ""
+            zeroByte: 0
+            zeroShort: 0
+            zeroInteger: 0
+            zeroLong: 0
+            zeroFloat: 0.0
+            zeroDouble: 0.0
+        }
+    }
+    {
+        id: "RestJsonClientIgnoresDefaultValuesIfMemberValuesArePresentInResponse"
+        documentation: "Client ignores default values if member values are present in the response."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        code: 200
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "defaultString": "bye",
+                "defaultBoolean": false,
+                "defaultList": ["a"],
+                "defaultDocumentMap": {"name": "Jack"},
+                "defaultDocumentString": "bye",
+                "defaultDocumentBoolean": false,
+                "defaultDocumentList": ["b"],
+                "defaultNullDocument": "notNull",
+                "defaultTimestamp": 2,
+                "defaultBlob": "aGk=",
+                "defaultByte": 2,
+                "defaultShort": 2,
+                "defaultInteger": 20,
+                "defaultLong": 200,
+                "defaultFloat": 2.0,
+                "defaultDouble": 2.0,
+                "defaultMap": {"name": "Jack"},
+                "defaultEnum": "BAR",
+                "defaultIntEnum": 2,
+                "emptyString": "foo",
+                "falseBoolean": true,
+                "emptyBlob": "aGk=",
+                "zeroByte": 1,
+                "zeroShort": 1,
+                "zeroInteger": 1,
+                "zeroLong": 1,
+                "zeroFloat": 1.0,
+                "zeroDouble": 1.0
+            }"""
+        params: {
+            defaultString: "bye"
+            defaultBoolean: false
+            defaultList: ["a"]
+            defaultDocumentMap: {name: "Jack"}
+            defaultDocumentString: "bye"
+            defaultDocumentBoolean: false
+            defaultDocumentList: ["b"]
+            defaultNullDocument: "notNull"
+            defaultTimestamp: 1
+            defaultBlob: "hi"
+            defaultByte: 2
+            defaultShort: 2
+            defaultInteger: 20
+            defaultLong: 200
+            defaultFloat: 2.0
+            defaultDouble: 2.0
+            defaultMap: {name: "Jack"}
+            defaultEnum: "BAR"
+            defaultIntEnum: 2
+            emptyString: "foo"
+            falseBoolean: true
+            emptyBlob: "hi"
+            zeroByte: 1
+            zeroShort: 1
+            zeroInteger: 1
+            zeroLong: 1
+            zeroFloat: 1.0
+            zeroDouble: 1.0
+        }
+    }
+    {
+        id: "RestJsonServerPopulatesDefaultsInResponseWhenMissingInParams"
+        documentation: "Server populates default values in response when missing in params."
+        appliesTo: "server"
+        tags: ["defaults"]
+        protocol: restJson1
+        code: 200
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "defaultString": "hi",
+                "defaultBoolean": true,
+                "defaultList": [],
+                "defaultDocumentMap": {},
+                "defaultDocumentString": "hi",
+                "defaultDocumentBoolean": true,
+                "defaultDocumentList": [],
+                "defaultTimestamp": 0,
+                "defaultBlob": "YWJj",
+                "defaultByte": 1,
+                "defaultShort": 1,
+                "defaultInteger": 10,
+                "defaultLong": 100,
+                "defaultFloat": 1.0,
+                "defaultDouble": 1.0,
+                "defaultMap": {},
+                "defaultEnum": "FOO",
+                "defaultIntEnum": 1,
+                "emptyString": "",
+                "falseBoolean": false,
+                "emptyBlob": "",
+                "zeroByte": 0,
+                "zeroShort": 0,
+                "zeroInteger": 0,
+                "zeroLong": 0,
+                "zeroFloat": 0.0,
+                "zeroDouble": 0.0
+            }"""
+        params: {}
+    }
+])
+
+@http(uri: "/OperationWithDefaults", method: "POST")
+operation OperationWithDefaults {
+    input := {
+        defaults: Defaults
+        clientOptionalDefaults: ClientOptionalDefaults
+        topLevelDefault: String = "hi" // Client should ignore default values in input shape
+        otherTopLevelDefault: Integer = 0
+    }
+
+    output := with [DefaultsMixin] {}
+}
+
+structure Defaults with [DefaultsMixin] {}
+
+structure ClientOptionalDefaults {
+    @clientOptional
+    member: Integer = 0
+}
+
+@mixin
+structure DefaultsMixin {
+    defaultString: String = "hi"
+    defaultBoolean: Boolean = true
+    defaultList: TestStringList = []
+    defaultDocumentMap: Document = {}
+    defaultDocumentString: Document = "hi"
+    defaultDocumentBoolean: Document = true
+    defaultDocumentList: Document = []
+    defaultNullDocument: Document = null
+    defaultTimestamp: Timestamp = 0
+    defaultBlob: Blob = "abc"
+    defaultByte: Byte = 1
+    defaultShort: Short = 1
+    defaultInteger: Integer = 10
+    defaultLong: Long = 100
+    defaultFloat: Float = 1.0
+    defaultDouble: Double = 1.0
+    defaultMap: TestStringMap = {}
+    defaultEnum: TestEnum = "FOO"
+    defaultIntEnum: TestIntEnum = 1
+    emptyString: String = ""
+    falseBoolean: Boolean = false
+    emptyBlob: Blob = ""
+    zeroByte: Byte = 0
+    zeroShort: Short = 0
+    zeroInteger: Integer = 0
+    zeroLong: Long = 0
+    zeroFloat: Float = 0.0
+    zeroDouble: Double = 0.0
+}
+
+list TestStringList {
+    member: String
+}
+
+map TestStringMap {
+    key: String
+    value: String
+}
+
+enum TestEnum {
+    FOO
+    BAR
+    BAZ
+}
+
+intEnum TestIntEnum {
+    ONE = 1
+    TWO = 2
+}
\ No newline at end of file
diff --git a/smithy-aws-protocol-tests/model/restJson1/main.smithy b/smithy-aws-protocol-tests/model/restJson1/main.smithy
index e5ae6f979..10c35886d 100644
--- a/smithy-aws-protocol-tests/model/restJson1/main.smithy
+++ b/smithy-aws-protocol-tests/model/restJson1/main.smithy
@@ -152,5 +152,9 @@ service RestJson {
 
         // requestCompression trait tests
         PutWithContentEncoding
+
+        // defaults
+        OperationWithDefaults
+        OperationWithNestedStructure
     ]
 }
diff --git a/smithy-aws-protocol-tests/model/restJson1/malformedRequests/malformed-union.smithy b/smithy-aws-protocol-tests/model/restJson1/malformedRequests/malformed-union.smithy
index cd0ca8cd4..ab4571cdb 100644
--- a/smithy-aws-protocol-tests/model/restJson1/malformedRequests/malformed-union.smithy
+++ b/smithy-aws-protocol-tests/model/restJson1/malformedRequests/malformed-union.smithy
@@ -78,6 +78,28 @@ apply MalformedUnion @httpMalformedRequestTests([
             }
         }
     },
+    {
+        id: "RestJsonMalformedUnionEmptyObjectNoFieldsSet",
+        documentation: """
+            When the union is an empty object, it has no fields set, so the
+            response should be a 400 SerializationException.""",
+        protocol: restJson1,
+        request: {
+            method: "POST",
+            uri: "/MalformedUnion",
+            body: """
+                { "union" : {  } }""",
+            headers: {
+                "content-type": "application/json"
+            }
+        },
+        response: {
+            code: 400,
+            headers: {
+                "x-amzn-errortype": "SerializationException"
+            }
+        }
+    },
     {
         id: "RestJsonMalformedUnionValueIsArray",
         documentation: """
diff --git a/smithy-aws-protocol-tests/model/restJson1/nested-defaults.smithy b/smithy-aws-protocol-tests/model/restJson1/nested-defaults.smithy
new file mode 100644
index 000000000..bba92f85e
--- /dev/null
+++ b/smithy-aws-protocol-tests/model/restJson1/nested-defaults.smithy
@@ -0,0 +1,409 @@
+$version: "2.0"
+
+namespace aws.protocoltests.restjson
+
+use aws.protocols#restJson1
+use smithy.test#httpRequestTests
+use smithy.test#httpResponseTests
+
+apply OperationWithNestedStructure @httpRequestTests([
+    {
+        id: "RestJsonClientPopulatesNestedDefaultValuesWhenMissing"
+        documentation: "Client populates nested default values when missing."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithNestedStructure"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "topLevel": {
+                    "dialog": {
+                        "language": "en",
+                        "greeting": "hi"
+                    },
+                    "dialogList": [
+                        {
+                            "greeting": "hi"
+                        },
+                        {
+                            "greeting": "hi",
+                            "farewell": {
+                                "phrase": "bye"
+                            }
+                        },
+                        {
+                            "language": "it",
+                            "greeting": "ciao",
+                            "farewell": {
+                                "phrase": "arrivederci"
+                            }
+                        }
+                    ],
+                    "dialogMap": {
+                        "emptyDialog": {
+                            "greeting": "hi"
+                        },
+                        "partialEmptyDialog": {
+                            "language": "en",
+                            "greeting": "hi",
+                            "farewell": {
+                                "phrase": "bye"
+                            }
+                        },
+                        "nonEmptyDialog": {
+                            "greeting": "konnichiwa",
+                            "farewell": {
+                                "phrase": "sayonara"
+                            }
+                        }
+                    }
+                }
+            }"""
+        params: {
+            "topLevel": {
+                "dialog": {
+                    "language": "en"
+                },
+                "dialogList": [
+                    {
+                    },
+                    {
+                        "farewell": {}
+                    },
+                    {
+                        "language": "it",
+                        "greeting": "ciao",
+                        "farewell": {
+                            "phrase": "arrivederci"
+                        }
+                    }
+                ],
+                "dialogMap": {
+                    "emptyDialog": {
+                    },
+                    "partialEmptyDialog": {
+                        "language": "en",
+                        "farewell": {}
+                    },
+                    "nonEmptyDialog": {
+                        "greeting": "konnichiwa",
+                        "farewell": {
+                            "phrase": "sayonara"
+                        }
+                    }
+                }
+            }
+        }
+    }
+    {
+        id: "RestJsonServerPopulatesNestedDefaultsWhenMissingInRequestBody"
+        documentation: "Server populates nested default values when missing in request body."
+        appliesTo: "server"
+        tags: ["defaults"]
+        protocol: restJson1
+        method: "POST"
+        bodyMediaType: "application/json"
+        uri: "/OperationWithNestedStructure"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "topLevel": {
+                    "dialog": {
+                        "language": "en"
+                    },
+                    "dialogList": [
+                        {
+                        },
+                        {
+                            "farewell": {}
+                        },
+                        {
+                            "language": "it",
+                            "greeting": "ciao",
+                            "farewell": {
+                                "phrase": "arrivederci"
+                            }
+                        }
+                    ],
+                    "dialogMap": {
+                        "emptyDialog": {
+                        },
+                        "partialEmptyDialog": {
+                            "language": "en",
+                            "farewell": {}
+                        },
+                        "nonEmptyDialog": {
+                            "greeting": "konnichiwa",
+                            "farewell": {
+                                "phrase": "sayonara"
+                            }
+                        }
+                    }
+                }
+            }"""
+        params: {
+            "topLevel": {
+                "dialog": {
+                    "language": "en"
+                    "greeting": "hi",
+                }
+                "dialogList": [
+                    {
+                        "greeting": "hi",
+                    },
+                    {
+                        "greeting": "hi",
+                        "farewell": {
+                            "phrase": "bye",
+                        }
+                    },
+                    {
+                        "language": "it",
+                        "greeting": "ciao",
+                        "farewell": {
+                            "phrase": "arrivederci"
+                        }
+                    }
+                ],
+                "dialogMap": {
+                    "emptyDialog": {
+                        "greeting": "hi",
+                    },
+                    "partialEmptyDialog": {
+                        "language": "en",
+                        "greeting": "hi",
+                        "farewell": {
+                            "phrase": "bye",
+                        }
+                    },
+                    "nonEmptyDialog": {
+                        "greeting": "konnichiwa",
+                        "farewell": {
+                            "phrase": "sayonara"
+                        }
+                    }
+                }
+            }
+        }
+    }
+])
+
+apply OperationWithNestedStructure @httpResponseTests([
+    {
+        id: "RestJsonClientPopulatesNestedDefaultsWhenMissingInResponseBody"
+        documentation: "Client populates nested default values when missing in response body."
+        appliesTo: "client"
+        tags: ["defaults"]
+        protocol: restJson1
+        code: 200
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "dialog": {
+                    "language": "en"
+                },
+                "dialogList": [
+                    {
+                    },
+                    {
+                        "farewell": {}
+                    },
+                    {
+                        "language": "it",
+                        "greeting": "ciao",
+                        "farewell": {
+                            "phrase": "arrivederci"
+                        }
+                    }
+                ],
+                "dialogMap": {
+                    "emptyDialog": {
+                    },
+                    "partialEmptyDialog": {
+                        "language": "en",
+                        "farewell": {}
+                    },
+                    "nonEmptyDialog": {
+                        "greeting": "konnichiwa",
+                        "farewell": {
+                            "phrase": "sayonara"
+                        }
+                    }
+                }
+            }"""
+        params: {
+            "dialog": {
+                "language": "en"
+                "greeting": "hi",
+            }
+            "dialogList": [
+                {
+                    "greeting": "hi",
+                },
+                {
+                    "greeting": "hi",
+                    "farewell": {
+                        "phrase": "bye",
+                    }
+                },
+                {
+                    "language": "it",
+                    "greeting": "ciao",
+                    "farewell": {
+                        "phrase": "arrivederci"
+                    }
+                }
+            ],
+            "dialogMap": {
+                "emptyDialog": {
+                    "greeting": "hi",
+                },
+                "partialEmptyDialog": {
+                    "language": "en",
+                    "greeting": "hi",
+                    "farewell": {
+                        "phrase": "bye",
+                    }
+                },
+                "nonEmptyDialog": {
+                    "greeting": "konnichiwa",
+                    "farewell": {
+                        "phrase": "sayonara"
+                    }
+                }
+            }
+        }
+    }
+    {
+        id: "RestJsonServerPopulatesNestedDefaultValuesWhenMissingInInResponseParams"
+        documentation: "Server populates nested default values when missing in response params."
+        appliesTo: "server"
+        tags: ["defaults"]
+        protocol: restJson1
+        code: 200
+        bodyMediaType: "application/json"
+        headers: {"Content-Type": "application/json"}
+        body: """
+            {
+                "dialog": {
+                    "language": "en",
+                    "greeting": "hi"
+                },
+                "dialogList": [
+                    {
+                        "greeting": "hi"
+                    },
+                    {
+                        "greeting": "hi",
+                        "farewell": {
+                            "phrase": "bye"
+                        }
+                    },
+                    {
+                        "language": "it",
+                        "greeting": "ciao",
+                        "farewell": {
+                            "phrase": "arrivederci"
+                        }
+                    }
+                ],
+                "dialogMap": {
+                    "emptyDialog": {
+                        "greeting": "hi"
+                    },
+                    "partialEmptyDialog": {
+                        "language": "en",
+                        "greeting": "hi",
+                        "farewell": {
+                            "phrase": "bye"
+                        }
+                    },
+                    "nonEmptyDialog": {
+                        "greeting": "konnichiwa",
+                        "farewell": {
+                            "phrase": "sayonara"
+                        }
+                    }
+                }
+            }"""
+        params: {
+            "dialog": {
+                "language": "en"
+            },
+            "dialogList": [
+                {
+                },
+                {
+                    "farewell": {}
+                },
+                {
+                    "language": "it",
+                    "greeting": "ciao",
+                    "farewell": {
+                        "phrase": "arrivederci"
+                    }
+                }
+            ],
+            "dialogMap": {
+                "emptyDialog": {
+                },
+                "partialEmptyDialog": {
+                    "language": "en",
+                    "farewell": {}
+                },
+                "nonEmptyDialog": {
+                    "greeting": "konnichiwa",
+                    "farewell": {
+                        "phrase": "sayonara"
+                    }
+                }
+            }
+        }
+    }
+])
+
+@http(uri: "/OperationWithNestedStructure", method: "POST")
+operation OperationWithNestedStructure {
+    input := {
+        @required
+        topLevel: TopLevel
+    }
+
+    output := with [NestedDefaultsMixin] {
+    }
+}
+
+structure TopLevel with [NestedDefaultsMixin] {
+
+}
+
+@mixin
+structure NestedDefaultsMixin {
+    @required
+    dialog: Dialog
+    dialogList: DialogList = []
+    dialogMap: DialogMap = {}
+}
+
+structure Dialog {
+    language: String
+    greeting: String = "hi"
+    farewell: Farewell
+}
+
+structure Farewell {
+    phrase: String = "bye"
+}
+
+list DialogList {
+    member: Dialog
+}
+
+map DialogMap {
+    key: String
+    value: Dialog
+}
\ No newline at end of file
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java
index 091284703..779a5f7b3 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/validation/NodeValidationVisitorTest.java
@@ -246,9 +246,9 @@ public class NodeValidationVisitorTest {
 
                 // http-date
                 {"ns.foo#HttpDate", "\"Tue, 29 Apr 2014 18:30:38 GMT\"", null},
-                {"ns.foo#HttpDate", "\"Tuesday, 29 April 2014 18:30:38 GMT\"", new String[] {"Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 7231 section-7.1.1.1. Found: Tuesday, 29 April 2014 18:30:38 GMT"}},
-                {"ns.foo#HttpDate", "\"Tue, 29 Apr 2014 18:30:38 PST\"", new String[] {"Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 7231 section-7.1.1.1. Found: Tue, 29 Apr 2014 18:30:38 PST"}},
-                {"ns.foo#HttpDate", "11", new String[] {"Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 7231 section-7.1.1.1. Found: number"}},
+                {"ns.foo#HttpDate", "\"Tuesday, 29 April 2014 18:30:38 GMT\"", new String[] {"Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 9110 section-5.6.7. Found: Tuesday, 29 April 2014 18:30:38 GMT"}},
+                {"ns.foo#HttpDate", "\"Tue, 29 Apr 2014 18:30:38 PST\"", new String[] {"Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 9110 section-5.6.7. Found: Tue, 29 Apr 2014 18:30:38 PST"}},
+                {"ns.foo#HttpDate", "11", new String[] {"Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 9110 section-5.6.7. Found: number"}},
 
                 // date-time
                 {"ns.foo#DateTime", "\"1985-04-12T23:20:50.52Z\"", null},
@@ -267,8 +267,8 @@ public class NodeValidationVisitorTest {
                         "0: Invalid string value, `1985-04-12T23:20:50.52-07:00`, provided for timestamp, `ns.foo#TimestampList$member`. Expected an RFC 3339 formatted timestamp (e.g., \"1985-04-12T23:20:50.52Z\")"
                 }},
                 {"ns.foo#TimestampList", "[123]", new String[] {"0: Expected a string value for a date-time timestamp (e.g., \"1985-04-12T23:20:50.52Z\")"}},
-                {"ns.foo#Structure4", "{\"httpDate\": 1234}", new String[] {"httpDate: Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 7231 section-7.1.1.1. Found: number"}},
-                {"ns.foo#Structure4", "{\"httpDateTarget\": 1234}", new String[] {"httpDateTarget: Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 7231 section-7.1.1.1. Found: number"}},
+                {"ns.foo#Structure4", "{\"httpDate\": 1234}", new String[] {"httpDate: Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 9110 section-5.6.7. Found: number"}},
+                {"ns.foo#Structure4", "{\"httpDateTarget\": 1234}", new String[] {"httpDateTarget: Invalid value provided for http-date formatted timestamp. Expected a string value that matches the IMF-fixdate production of RFC 9110 section-5.6.7. Found: number"}},
 
                 // timestamp member with no format.
                 {"ns.foo#TimestampListNoFormatTrait", "[123]", null},
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-header-tchar-validator.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-header-tchar-validator.errors
index f1242ede6..b0650ee97 100644
--- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-header-tchar-validator.errors
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/http-header-tchar-validator.errors
@@ -1,1 +1,1 @@
-[DANGER] ns.foo#SayHelloInput$myHeader: `Not valid!` is not a valid HTTP header field name according to section 3.2 of RFC 7230 | HttpHeaderTrait
+[DANGER] ns.foo#SayHelloInput$myHeader: `Not valid!` is not a valid HTTP header field name according to section 5.6.2 of RFC 9110 | HttpHeaderTrait
diff --git a/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.errors b/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.errors
index 0ac035113..9af8d2d2a 100644
--- a/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.errors
+++ b/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.errors
@@ -4,9 +4,14 @@
 [WARNING] example#GetResourceInput$ResourceId: This shape applies a trait that is unstable: smithy.rules#contextParam | UnstableTrait
 [WARNING] example#GetAnotherResource: This shape applies a trait that is unstable: smithy.rules#staticContextParams | UnstableTrait
 [WARNING] example#GetResource: This shape applies a trait that is unstable: smithy.rules#staticContextParams | UnstableTrait
+[WARNING] example#GetResource: This shape applies a trait that is unstable: smithy.rules#operationContextParams | UnstableTrait.smithy.rules#operationContextParams
+[WARNING] example#GetAnotherResource: This shape applies a trait that is unstable: smithy.rules#operationContextParams | UnstableTrait.smithy.rules#operationContextParams
 [ERROR] example#GetResource: Inconsistent type for `InconsistentParamType` parameter | RuleSetParameter.StaticContextParams.InconsistentType
 [ERROR] example#FizzBuzz: Type mismatch for parameter `ParameterFoo` | RuleSetParameter.RuleSet.TypeMismatch
 [ERROR] example#FizzBuzz: Parameter `ExtraParameter` exists in ruleset but not in service model | RuleSetParameter.RuleSet.UnmatchedName
 [ERROR] example#FizzBuzz: Parameter `AnotherParameterBar` exists in service model but not in ruleset | RuleSetParameter.RuleSet.UnmatchedName
 [ERROR] example#FizzBuzz: Parameter `InconsistentParamType` exists in service model but not in ruleset | RuleSetParameter.RuleSet.UnmatchedName
 [ERROR] example#FizzBuzz: Parameter `ParamNotInRuleset` exists in service model but not in ruleset | RuleSetParameter.RuleSet.UnmatchedName
+[ERROR] example#FizzBuzz: Parameter `InconsistentOperactionContextParam` exists in service model but not in ruleset | RuleSetParameter.RuleSet.UnmatchedName
+[ERROR] example#FizzBuzz: Type mismatch for parameter `StringArrayParam` | RuleSetParameter.RuleSet.TypeMismatch
+[ERROR] example#GetResource: Inconsistent type for `InconsistentOperactionContextParam` parameter | RuleSetParameter.OperationContextParams.InconsistentType
diff --git a/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.smithy b/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.smithy
index c3e9e39e4..4c64d3adc 100644
--- a/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.smithy
+++ b/smithy-rules-engine/src/test/resources/software/amazon/smithy/rulesengine/language/errorfiles/invalid/inconsistent-params.smithy
@@ -5,6 +5,7 @@ namespace example
 use smithy.rules#clientContextParams
 use smithy.rules#contextParam
 use smithy.rules#endpointRuleSet
+use smithy.rules#operationContextParams
 use smithy.rules#staticContextParams
 
 @endpointRuleSet({
@@ -30,6 +31,10 @@ use smithy.rules#staticContextParams
             "type": "string",
             "documentation": "docs",
             "builtIn": "SDK::Endpoint"
+        },
+        "StringArrayParam": {
+            "type": "stringArray",
+            "documentation": "docs"
         }
     },
     "rules": []
@@ -47,6 +52,10 @@ service FizzBuzz {
     "ParamNotInRuleset": {value: "someValue"},
     "InconsistentParamType": {value: true}
 )
+@operationContextParams(
+    "StringArrayParam": {path: "ResourceId"},
+    "InconsistentOperactionContextParam": {path: "ResourceId"}
+)
 operation GetResource {
     input: GetResourceInput
 }
@@ -61,13 +70,21 @@ structure GetResourceInput {
     "ParamNotInRuleset": {value: "someOtherValue"},
     "InconsistentParamType": {value: "someValue"}
 )
+@operationContextParams(
+    "InconsistentOperactionContextParam": {path: "ListOfStrings[*]"}
+)
 operation GetAnotherResource {
     input: GetAnotherResourceInput
 }
 
 structure GetAnotherResourceInput {
     @contextParam(name: "AnotherParameterBar")
-    ResourceId: ResourceId
+    ResourceId: ResourceId,
+    ListOfStrings: ListOfStrings
+}
+
+list ListOfStrings {
+    member: String
 }
 
 string ResourceId