Repository: smithy-lang/smithy
Pull request #2439 (2024-11-01 17:50:36)

Add validator for xmlFlattened + xmlName
Adds a validator for the xmlFlattened trait that checks if the member's target is a list that has a member with xmlName, and that xmlName doesn't match the name of the xmlFlattened member. xmlFlattened causes the xmlName of the list member to be ignored. This is called out in our docs:
https://smithy.io/2.0/spec/protocol-traits.html#flattened-list-serialization, but we were missing a validator for it. It is a warning because you might mean to do this, i.e. if the list is used in multiple places.

I also added some suppressions to our protocol tests that are testing the behavior for models that would trip up this validator.

---
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/Makefile b/docs/Makefile
index 48d1df588..e668e256d 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -7,7 +7,6 @@ SHELL         := /bin/bash
 install: requirements.txt
 	python3 -m venv build/venv
 	source build/venv/bin/activate && pip3 install -r requirements.txt . && pip3 install -e .
-	touch install
 
 clean:
 	-rm -rf build/* install
diff --git a/docs/source-2.0/aws/aws-core.rst b/docs/source-2.0/aws/aws-core.rst
index fb961d890..be11b13da 100644
--- a/docs/source-2.0/aws/aws-core.rst
+++ b/docs/source-2.0/aws/aws-core.rst
@@ -290,14 +290,25 @@ members:
       - Set to true to specify that the ARN does not contain an account ID.
         If not set, or if set to false, the resolved ARN will contain a
         placeholder for the customer account ID. This can only be set to
-        true if absolute is not set or is false.
+        true if ``absolute`` is not set or is false.
     * - absolute
       - ``boolean``
       - Set to true to indicate that the ARN template contains a fully-formed
         ARN that does not need to be merged with the service. This type of
         ARN MUST be used when the identifier of a resource is an ARN or is
         based on the ARN identifier of a parent resource.
+    * - resourceDelimiter
+      - ``string``
+      - Indicates which character is used to delimit sections of the resource
+        segment of an ARN. This can only be set if ``absolute`` is set to true.
+        Valid values are ``/`` (forward slash) and ``:`` (colon).
+    * - reusable
+      - ``boolean``
+      - Set to true to indicate that an ARN may be reused for different
+        instances of a resource.
+
 
+.. _aws.api#arn-trait_format-of-an-arn:
 
 Format of an ARN
 ================
@@ -354,6 +365,8 @@ Some example ARNs from various services include:
     arn:aws:s3:::my_corporate_bucket/exampleobject.png
 
 
+.. _aws.api#arn-trait_relative-arn-templates:
+
 Relative ARN templates
 ======================
 
@@ -391,6 +404,8 @@ identifier is to be inserted into the ARN template when resolving it at
 runtime.
 
 
+.. _aws.api#arn-trait_using-an-arn-as-a-resource-identifier:
+
 Using an ARN as a resource identifier
 =====================================
 
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 81782ae64..b9c2a48e3 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
@@ -804,6 +804,26 @@ mapStrategy (``string``)
             }
         }
 
+.. _generate-openapi-jsonschema-setting-useInlineMaps:
+
+useInlineMaps (``boolean``)
+    Configures Smithy to generate ``map`` shapes inline instead of as
+    references. This is necessary for some code generators to distinguish
+    between ``maps`` and ``structure`` shapes when generating.
+
+    .. code-block:: json
+        :caption: smithy-build.json
+
+        {
+            "version": "1.0",
+            "plugins": {
+                "openapi": {
+                    "service": "example.weather#Weather",
+                    "useInlineMaps": true
+                }
+            }
+        }
+
 .. _generate-openapi-jsonschema-setting-schemaDocumentExtensions:
 
 schemaDocumentExtensions (``Map<String, any>``)
diff --git a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/CfnConverter.java b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/CfnConverter.java
index c6c79e352..d04fe934c 100644
--- a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/CfnConverter.java
+++ b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/CfnConverter.java
@@ -25,16 +25,14 @@ import java.util.ServiceLoader;
 import java.util.Set;
 import software.amazon.smithy.aws.cloudformation.schema.CfnConfig;
 import software.amazon.smithy.aws.cloudformation.schema.CfnException;
+import software.amazon.smithy.aws.cloudformation.schema.fromsmithy.mappers.TaggingMapper;
 import software.amazon.smithy.aws.cloudformation.schema.model.Property;
 import software.amazon.smithy.aws.cloudformation.schema.model.ResourceSchema;
-import software.amazon.smithy.aws.cloudformation.schema.model.Tagging;
 import software.amazon.smithy.aws.cloudformation.traits.CfnNameTrait;
 import software.amazon.smithy.aws.cloudformation.traits.CfnResource;
 import software.amazon.smithy.aws.cloudformation.traits.CfnResourceIndex;
 import software.amazon.smithy.aws.cloudformation.traits.CfnResourceTrait;
 import software.amazon.smithy.aws.traits.ServiceTrait;
-import software.amazon.smithy.aws.traits.tagging.AwsTagIndex;
-import software.amazon.smithy.aws.traits.tagging.TaggableTrait;
 import software.amazon.smithy.jsonschema.JsonSchemaConverter;
 import software.amazon.smithy.jsonschema.JsonSchemaMapper;
 import software.amazon.smithy.jsonschema.PropertyNamingStrategy;
@@ -56,7 +54,6 @@ import software.amazon.smithy.utils.ListUtils;
 import software.amazon.smithy.utils.StringUtils;
 
 public final class CfnConverter {
-    private static final String DEFAULT_TAGS_NAME = "Tags";
     private ClassLoader classLoader = CfnConverter.class.getClassLoader();
     private CfnConfig config = new CfnConfig();
     private final List<Smithy2CfnExtension> extensions = new ArrayList<>();
@@ -305,20 +302,6 @@ public final class CfnConverter {
             builder.addDefinition(definitionName, definition.getValue());
         }
 
-        if (resourceShape.hasTrait(TaggableTrait.class)) {
-            AwsTagIndex tagsIndex = AwsTagIndex.of(environment.context.getModel());
-            TaggableTrait trait = resourceShape.expectTrait(TaggableTrait.class);
-            Tagging.Builder tagBuilder = Tagging.builder()
-                    .taggable(true)
-                    .tagOnCreate(tagsIndex.isResourceTagOnCreate(resourceShape.getId()))
-                    .tagProperty(getTagsProperty(resourceShape))
-                    .cloudFormationSystemTags(!trait.getDisableSystemTags())
-                    // Unless tag-on-create is supported, Smithy tagging means
-                    .tagUpdatable(true);
-
-            builder.tagging(tagBuilder.build());
-        }
-
         // Apply all the mappers' after methods.
         ResourceSchema resourceSchema = builder.build();
         for (CfnMapper mapper : environment.mappers) {
@@ -391,47 +374,8 @@ public final class CfnConverter {
             }
         });
 
-        injectTagsIfNecessary(builder, model, resource, cfnResource);
+        TaggingMapper.injectTagsMember(config, model, resource, builder);
 
         return builder.build();
     }
-
-    private String getTagsProperty(ResourceShape resource) {
-        return resource.getTrait(TaggableTrait.class)
-                .flatMap(TaggableTrait::getProperty)
-                .map(property -> {
-                    if (config.getDisableCapitalizedProperties()) {
-                        return property;
-                    }
-                    return StringUtils.capitalize(property);
-                })
-                .orElse(DEFAULT_TAGS_NAME);
-    }
-
-    private void injectTagsIfNecessary(
-        StructureShape.Builder builder,
-        Model model,
-        ResourceShape resource,
-        CfnResource cfnResource
-    ) {
-        String tagPropertyName = getTagsProperty(resource);
-        if (resource.hasTrait(TaggableTrait.class)) {
-            AwsTagIndex tagIndex = AwsTagIndex.of(model);
-            TaggableTrait trait = resource.expectTrait(TaggableTrait.class);
-            if (!trait.getProperty().isPresent() || !cfnResource.getProperties()
-                    .containsKey(trait.getProperty().get())) {
-                if (trait.getProperty().isPresent()) {
-                    ShapeId definition = resource.getProperties().get(trait.getProperty().get());
-                    builder.addMember(tagPropertyName, definition);
-                } else {
-                    // A valid TagResource operation certainly has a single tags input member.
-                    AwsTagIndex awsTagIndex = AwsTagIndex.of(model);
-                    Optional<ShapeId> tagOperation = tagIndex.getTagResourceOperation(resource.getId());
-                    MemberShape member = awsTagIndex.getTagsMember(tagOperation.get()).get();
-                    member = member.toBuilder().id(builder.getId().withMember(tagPropertyName)).build();
-                    builder.addMember(member);
-                }
-            }
-        }
-    }
 }
diff --git a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/CoreExtension.java b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/CoreExtension.java
index 4b75d220d..38cadd973 100644
--- a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/CoreExtension.java
+++ b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/CoreExtension.java
@@ -34,6 +34,7 @@ public final class CoreExtension implements Smithy2CfnExtension {
                 new IdentifierMapper(),
                 new JsonAddMapper(),
                 new MutabilityMapper(),
-                new RequiredMapper());
+                new RequiredMapper(),
+                new TaggingMapper());
     }
 }
diff --git a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapper.java b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapper.java
index 9a449c98a..ba45cbbb7 100644
--- a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapper.java
+++ b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/HandlerPermissionMapper.java
@@ -40,7 +40,7 @@ import software.amazon.smithy.utils.SmithyInternalApi;
  * @see <a href="https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html#schema-properties-handlers">handlers Docs</a>
  */
 @SmithyInternalApi
-public final class HandlerPermissionMapper implements CfnMapper {
+final class HandlerPermissionMapper implements CfnMapper {
     @Override
     public void before(Context context, ResourceSchema.Builder resourceSchema) {
         if (context.getConfig().getDisableHandlerPermissionGeneration()) {
@@ -97,7 +97,7 @@ public final class HandlerPermissionMapper implements CfnMapper {
                         .permissions(permissions).build()));
     }
 
-    private Set<String> getPermissionsEntriesForOperation(Model model, ServiceShape service, ShapeId operationId) {
+    static Set<String> getPermissionsEntriesForOperation(Model model, ServiceShape service, ShapeId operationId) {
         OperationShape operation = model.expectShape(operationId, OperationShape.class);
         Set<String> permissionsEntries = new TreeSet<>();
 
diff --git a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/TaggingMapper.java b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/TaggingMapper.java
new file mode 100644
index 000000000..3ffad4609
--- /dev/null
+++ b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/mappers/TaggingMapper.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.cloudformation.schema.fromsmithy.mappers;
+
+import static software.amazon.smithy.aws.cloudformation.schema.fromsmithy.mappers.HandlerPermissionMapper.getPermissionsEntriesForOperation;
+
+import java.util.Optional;
+import software.amazon.smithy.aws.cloudformation.schema.CfnConfig;
+import software.amazon.smithy.aws.cloudformation.schema.fromsmithy.CfnMapper;
+import software.amazon.smithy.aws.cloudformation.schema.fromsmithy.Context;
+import software.amazon.smithy.aws.cloudformation.schema.model.ResourceSchema;
+import software.amazon.smithy.aws.cloudformation.schema.model.Tagging;
+import software.amazon.smithy.aws.cloudformation.traits.CfnResource;
+import software.amazon.smithy.aws.cloudformation.traits.CfnResourceIndex;
+import software.amazon.smithy.aws.traits.tagging.AwsTagIndex;
+import software.amazon.smithy.aws.traits.tagging.TaggableTrait;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.MemberShape;
+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.shapes.StructureShape;
+import software.amazon.smithy.utils.SmithyInternalApi;
+import software.amazon.smithy.utils.StringUtils;
+
+/**
+ * Generates the resource's Tagging configuration based on the AwsTagIndex, including
+ * the tagging property and operations that interact with tags.
+ *
+ * @see <a href="https://github.com/aws-cloudformation/cloudformation-cli/blob/master/src/rpdk/core/data/schema/provider.definition.schema.v1.json#L198">permissions property definition</a>
+ */
+@SmithyInternalApi
+public final class TaggingMapper implements CfnMapper {
+    private static final String DEFAULT_TAGS_NAME = "Tags";
+
+    @SmithyInternalApi
+    public static void injectTagsMember(
+            CfnConfig config,
+            Model model,
+            ResourceShape resource,
+            StructureShape.Builder builder
+    ) {
+        String tagMemberName = getTagMemberName(config, resource);
+        if (resource.hasTrait(TaggableTrait.class)) {
+            AwsTagIndex tagIndex = AwsTagIndex.of(model);
+            TaggableTrait trait = resource.expectTrait(TaggableTrait.class);
+            CfnResourceIndex resourceIndex = CfnResourceIndex.of(model);
+            CfnResource cfnResource = resourceIndex.getResource(resource).get();
+
+            if (!trait.getProperty().isPresent() || !cfnResource.getProperties()
+                    .containsKey(trait.getProperty().get())) {
+                if (trait.getProperty().isPresent()) {
+                    ShapeId definition = resource.getProperties().get(trait.getProperty().get());
+                    builder.addMember(tagMemberName, definition);
+                } else {
+                    // A valid TagResource operation certainly has a single tags input member.
+                    Optional<ShapeId> tagOperation = tagIndex.getTagResourceOperation(resource.getId());
+                    MemberShape member = tagIndex.getTagsMember(tagOperation.get()).get();
+                    member = member.toBuilder().id(builder.getId().withMember(tagMemberName)).build();
+                    builder.addMember(member);
+                }
+            }
+        }
+    }
+
+    @Override
+    public ResourceSchema after(Context context, ResourceSchema resourceSchema) {
+        ResourceShape resourceShape = context.getResource();
+        if (!resourceShape.hasTrait(TaggableTrait.class)) {
+            return resourceSchema;
+        }
+
+        Model model = context.getModel();
+        ServiceShape service = context.getService();
+        AwsTagIndex tagsIndex = AwsTagIndex.of(model);
+        TaggableTrait trait = resourceShape.expectTrait(TaggableTrait.class);
+        Tagging.Builder tagBuilder = Tagging.builder()
+                .taggable(true)
+                .tagOnCreate(tagsIndex.isResourceTagOnCreate(resourceShape.getId()))
+                .tagProperty("/properties/" + getTagMemberName(context.getConfig(), resourceShape))
+                .cloudFormationSystemTags(!trait.getDisableSystemTags())
+                // Unless tag-on-create is supported, Smithy tagging means
+                .tagUpdatable(true);
+
+        // Add the tagging permissions based on the defined tagging operations.
+        tagsIndex.getTagResourceOperation(resourceShape)
+                .map(operation -> getPermissionsEntriesForOperation(model, service, operation))
+                .ifPresent(tagBuilder::addPermissions);
+        tagsIndex.getListTagsForResourceOperation(resourceShape)
+                .map(operation -> getPermissionsEntriesForOperation(model, service, operation))
+                .ifPresent(tagBuilder::addPermissions);
+        tagsIndex.getUntagResourceOperation(resourceShape)
+                .map(operation -> getPermissionsEntriesForOperation(model, service, operation))
+                .ifPresent(tagBuilder::addPermissions);
+
+        return resourceSchema.toBuilder().tagging(tagBuilder.build()).build();
+    }
+
+    private static String getTagMemberName(CfnConfig config, ResourceShape resource) {
+        return resource.getTrait(TaggableTrait.class)
+                .flatMap(TaggableTrait::getProperty)
+                .map(property -> {
+                    if (config.getDisableCapitalizedProperties()) {
+                        return property;
+                    }
+                    return StringUtils.capitalize(property);
+                })
+                .orElse(DEFAULT_TAGS_NAME);
+    }
+}
diff --git a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/model/Tagging.java b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/model/Tagging.java
index cd429ca79..4a3073044 100644
--- a/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/model/Tagging.java
+++ b/smithy-aws-cloudformation/src/main/java/software/amazon/smithy/aws/cloudformation/schema/model/Tagging.java
@@ -15,6 +15,10 @@
 
 package software.amazon.smithy.aws.cloudformation.schema.model;
 
+import java.util.Collection;
+import java.util.Set;
+import java.util.TreeSet;
+import software.amazon.smithy.utils.SetUtils;
 import software.amazon.smithy.utils.SmithyBuilder;
 import software.amazon.smithy.utils.ToSmithyBuilder;
 
@@ -27,6 +31,7 @@ public final class Tagging implements ToSmithyBuilder<Tagging> {
     private final boolean tagUpdatable;
     private final String tagProperty;
     private final boolean cloudFormationSystemTags;
+    private final Set<String> permissions;
 
     private Tagging(Builder builder) {
         taggable = builder.taggable;
@@ -34,6 +39,7 @@ public final class Tagging implements ToSmithyBuilder<Tagging> {
         tagUpdatable = builder.tagUpdatable;
         cloudFormationSystemTags = builder.cloudFormationSystemTags;
         tagProperty = builder.tagProperty;
+        this.permissions = SetUtils.orderedCopyOf(builder.permissions);
     }
 
     public static Builder builder() {
@@ -85,6 +91,15 @@ public final class Tagging implements ToSmithyBuilder<Tagging> {
         return tagProperty;
     }
 
+    /**
+     * Returns the set of permissions required to interact with this resource's tags.
+     *
+     * @return the set of permissions.
+     */
+    public Set<String> getPermissions() {
+        return permissions;
+    }
+
     @Override
     public Builder toBuilder() {
         return builder()
@@ -92,7 +107,8 @@ public final class Tagging implements ToSmithyBuilder<Tagging> {
                 .tagOnCreate(tagOnCreate)
                 .tagUpdatable(tagUpdatable)
                 .cloudFormationSystemTags(cloudFormationSystemTags)
-                .tagProperty(tagProperty);
+                .tagProperty(tagProperty)
+                .permissions(permissions);
     }
 
     public static final class Builder implements SmithyBuilder<Tagging> {
@@ -101,6 +117,7 @@ public final class Tagging implements ToSmithyBuilder<Tagging> {
         private boolean tagUpdatable;
         private boolean cloudFormationSystemTags;
         private String tagProperty;
+        private final Set<String> permissions = new TreeSet<>();
 
         @Override
         public Tagging build() {
@@ -131,5 +148,28 @@ public final class Tagging implements ToSmithyBuilder<Tagging> {
             this.tagProperty = tagProperty;
             return this;
         }
+
+        public Builder permissions(Collection<String> permissions) {
+            this.permissions.clear();
+            this.permissions.addAll(permissions);
+            return this;
+        }
+
+        public Builder addPermissions(Collection<String> permissions) {
+            for (String permission : permissions) {
+                addPermission(permission);
+            }
+            return this;
+        }
+
+        public Builder addPermission(String permission) {
+            this.permissions.add(permission);
+            return this;
+        }
+
+        public Builder clearPermissions() {
+            this.permissions.clear();
+            return this;
+        }
     }
 }
diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/ArnTrait.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/ArnTrait.java
index c1ab461f6..3f7131dcb 100644
--- a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/ArnTrait.java
+++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/ArnTrait.java
@@ -19,11 +19,13 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import software.amazon.smithy.model.SourceException;
 import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.model.node.ObjectNode;
+import software.amazon.smithy.model.node.ToNode;
 import software.amazon.smithy.model.shapes.ShapeId;
 import software.amazon.smithy.model.traits.AbstractTrait;
 import software.amazon.smithy.model.traits.AbstractTraitBuilder;
@@ -42,6 +44,8 @@ public final class ArnTrait extends AbstractTrait implements ToSmithyBuilder<Arn
     private static final String ABSOLUTE = "absolute";
     private static final String NO_REGION = "noRegion";
     private static final String NO_ACCOUNT = "noAccount";
+    private static final String RESOURCE_DELIMITER = "resourceDelimiter";
+    private static final String REUSABLE = "reusable";
     private static final Pattern PATTERN = Pattern.compile("\\{([^}]+)}");
 
     private final boolean noRegion;
@@ -49,14 +53,18 @@ public final class ArnTrait extends AbstractTrait implements ToSmithyBuilder<Arn
     private final boolean absolute;
     private final String template;
     private final List<String> labels;
+    private final ResourceDelimiter resourceDelimiter;
+    private final boolean reusable;
 
     private ArnTrait(Builder builder) {
         super(ID, builder.getSourceLocation());
         this.template = SmithyBuilder.requiredState(TEMPLATE, builder.template);
+        this.labels = Collections.unmodifiableList(parseLabels(template));
         this.noRegion = builder.noRegion;
         this.noAccount = builder.noAccount;
         this.absolute = builder.absolute;
-        this.labels = Collections.unmodifiableList(parseLabels(template));
+        this.resourceDelimiter = builder.resourceDelimiter;
+        this.reusable = builder.reusable;
 
         if (template.startsWith("/")) {
             throw new SourceException("Invalid aws.api#arn trait. The template must not start with '/'. "
@@ -77,6 +85,8 @@ public final class ArnTrait extends AbstractTrait implements ToSmithyBuilder<Arn
             builder.absolute(objectNode.getBooleanMemberOrDefault(ABSOLUTE));
             builder.noRegion(objectNode.getBooleanMemberOrDefault(NO_REGION));
             builder.noAccount(objectNode.getBooleanMemberOrDefault(NO_ACCOUNT));
+            objectNode.getStringMember(RESOURCE_DELIMITER, builder::resourceDelimiter);
+            builder.reusable(objectNode.getBooleanMemberOrDefault(REUSABLE));
             ArnTrait result = builder.build();
             result.setNodeCache(value);
             return result;
@@ -128,12 +138,26 @@ public final class ArnTrait extends AbstractTrait implements ToSmithyBuilder<Arn
     }
 
     /**
-     * @return Returns the label place holder variable names.
+     * @return Returns the label placeholder variable names.
      */
     public List<String> getLabels() {
         return labels;
     }
 
+    /**
+     * @return Returns the resource delimiter for absolute ARNs.
+     */
+    public Optional<ResourceDelimiter> getResourceDelimiter() {
+        return Optional.ofNullable(resourceDelimiter);
+    }
+
+    /**
+     * @return Returns if the ARN may be reused for different instances of a resource.
+     */
+    public boolean isReusable() {
+        return reusable;
+    }
+
     @Override
     protected Node createNode() {
         return Node.objectNodeBuilder()
@@ -142,6 +166,8 @@ public final class ArnTrait extends AbstractTrait implements ToSmithyBuilder<Arn
                 .withMember(ABSOLUTE, Node.from(isAbsolute()))
                 .withMember(NO_ACCOUNT, Node.from(isNoAccount()))
                 .withMember(NO_REGION, Node.from(isNoRegion()))
+                .withOptionalMember(RESOURCE_DELIMITER, getResourceDelimiter())
+                .withMember(REUSABLE, Node.from(isReusable()))
                 .build();
     }
 
@@ -151,11 +177,14 @@ public final class ArnTrait extends AbstractTrait implements ToSmithyBuilder<Arn
                 .sourceLocation(getSourceLocation())
                 .noRegion(isNoRegion())
                 .noAccount(isNoAccount())
-                .template(getTemplate());
+                .absolute(isAbsolute())
+                .template(getTemplate())
+                .resourceDelimiter(resourceDelimiter)
+                .reusable(reusable);
     }
 
     // Due to the defaulting of this trait, equals has to be overridden
-    // so that inconsequential differences in toNode do not effect equality.
+    // so that inconsequential differences in toNode do not affect equality.
     @Override
     public boolean equals(Object other) {
         if (!(other instanceof ArnTrait)) {
@@ -167,13 +196,45 @@ public final class ArnTrait extends AbstractTrait implements ToSmithyBuilder<Arn
             return template.equals(oa.template)
                     && absolute == oa.absolute
                     && noAccount == oa.noAccount
-                    && noRegion == oa.noRegion;
+                    && noRegion == oa.noRegion
+                    && resourceDelimiter == oa.resourceDelimiter
+                    && reusable == oa.reusable;
         }
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(toShapeId(), template, absolute, noAccount, noRegion);
+        return Objects.hash(toShapeId(), template, absolute, noAccount, noRegion, resourceDelimiter, reusable);
+    }
+
+    public enum ResourceDelimiter implements ToNode {
+        FORWARD_SLASH("/"),
+        COLON(":");
+
+        private final String value;
+
+        ResourceDelimiter(String value) {
+            this.value = value;
+        }
+
+        static ResourceDelimiter from(String value) {
+            for (ResourceDelimiter delimiter : values()) {
+                if (delimiter.value.equals(value)) {
+                    return delimiter;
+                }
+            }
+            throw new IllegalArgumentException("Invalid delimiter: " + value);
+        }
+
+        @Override
+        public String toString() {
+            return value;
+        }
+
+        @Override
+        public Node toNode() {
+            return Node.from(value);
+        }
     }
 
     /** Builder for {@link ArnTrait}. */
@@ -182,6 +243,8 @@ public final class ArnTrait extends AbstractTrait implements ToSmithyBuilder<Arn
         private boolean noAccount;
         private boolean absolute;
         private String template;
+        private ResourceDelimiter resourceDelimiter;
+        private boolean reusable;
 
         private Builder() {}
 
@@ -209,5 +272,20 @@ public final class ArnTrait extends AbstractTrait implements ToSmithyBuilder<Arn
             this.noRegion = noRegion;
             return this;
         }
+
+        public Builder resourceDelimiter(ResourceDelimiter resourceDelimiter) {
+            this.resourceDelimiter = resourceDelimiter;
+            return this;
+        }
+
+        public Builder resourceDelimiter(String resourceDelimiter) {
+            this.resourceDelimiter = ResourceDelimiter.from(resourceDelimiter);
+            return this;
+        }
+
+        public Builder reusable(boolean reusable) {
+            this.reusable = reusable;
+            return this;
+        }
     }
 }
diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/ArnTraitValidator.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/ArnTraitValidator.java
new file mode 100644
index 000000000..889af6934
--- /dev/null
+++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/ArnTraitValidator.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package software.amazon.smithy.aws.traits;
+
+import java.util.ArrayList;
+import java.util.List;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.ResourceShape;
+import software.amazon.smithy.model.validation.AbstractValidator;
+import software.amazon.smithy.model.validation.ValidationEvent;
+import software.amazon.smithy.utils.SmithyInternalApi;
+
+/**
+ * Validates that the `resourceDelimiter` field of the `aws.api#arn` trait
+ * is only used in conjunction with absolute ARNs.
+ */
+@SmithyInternalApi
+public final class ArnTraitValidator extends AbstractValidator {
+    @Override
+    public List<ValidationEvent> validate(Model model) {
+        List<ValidationEvent> events = new ArrayList<>();
+        for (ResourceShape resource : model.getResourceShapesWithTrait(ArnTrait.class)) {
+            ArnTrait trait = resource.expectTrait(ArnTrait.class);
+            if (!trait.isAbsolute() && trait.getResourceDelimiter().isPresent()) {
+                events.add(error(resource, trait, "A `resourceDelimiter` can only be set for an `absolute` ARN."));
+            }
+        }
+        return events;
+    }
+}
diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/tagging/TaggableResourceValidator.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/tagging/TaggableResourceValidator.java
index 417bc1627..2dfa663ad 100644
--- a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/tagging/TaggableResourceValidator.java
+++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/tagging/TaggableResourceValidator.java
@@ -43,14 +43,23 @@ public final class TaggableResourceValidator extends AbstractValidator {
         List<ValidationEvent> events = new LinkedList<>();
         TopDownIndex topDownIndex = TopDownIndex.of(model);
         AwsTagIndex tagIndex = AwsTagIndex.of(model);
-        for (ServiceShape service : model.getServiceShapesWithTrait(TagEnabledTrait.class)) {
+        for (ServiceShape service : model.getServiceShapes()) {
             for (ResourceShape resource : topDownIndex.getContainedResources(service)) {
+                boolean resourceLikelyTaggable = false;
                 if (resource.hasTrait(TaggableTrait.class)) {
                     events.addAll(validateResource(model, resource, service, tagIndex));
-                } else if (resource.hasTrait(ArnTrait.class) && tagIndex.serviceHasTagApis(service.getId())) {
+                    resourceLikelyTaggable = true;
+                } else if (resource.hasTrait(ArnTrait.class) && tagIndex.serviceHasTagApis(service)) {
                     // If a resource does not have the taggable trait, but has an ARN, and the service has tag
                     // operations, it is most likely a mistake.
                     events.add(warning(resource, "Resource is likely missing `aws.api#taggable` trait."));
+                    resourceLikelyTaggable = true;
+                }
+
+                // It's possible the resource was marked as taggable but the service isn't tagEnabled.
+                if (resourceLikelyTaggable && !service.hasTrait(TagEnabledTrait.class)) {
+                    events.add(warning(service, "Service has resources with `aws.api#taggable` applied but does not "
+                            + "have the `aws.api#tagEnabled` trait."));
                 }
             }
         }
diff --git a/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
index 80a840407..133106fc8 100644
--- a/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
+++ b/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
@@ -1,4 +1,5 @@
 software.amazon.smithy.aws.traits.ArnTemplateValidator
+software.amazon.smithy.aws.traits.ArnTraitValidator
 software.amazon.smithy.aws.traits.HttpChecksumTraitValidator
 software.amazon.smithy.aws.traits.SdkServiceIdValidator
 software.amazon.smithy.aws.traits.clientendpointdiscovery.ClientEndpointDiscoveryValidator
diff --git a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.api.smithy b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.api.smithy
index baf9cb499..a079ec698 100644
--- a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.api.smithy
+++ b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.api.smithy
@@ -38,6 +38,14 @@ structure arn {
     /// for the customer account ID. This can only be set to true if absolute
     /// is not set or is false.
     noAccount: Boolean
+
+    /// Defines which character is used to delimit sections of the resource
+    /// segment of an ARN. This can only be set if absolute is set to true.
+    resourceDelimiter: ResourceDelimiter
+
+    /// Set to true to indicate that an ARN may be reused for different
+    /// instances of a resource.
+    reusable: Boolean
 }
 
 /// Marks a string as containing an ARN.
@@ -262,6 +270,17 @@ structure taggable {
     disableSystemTags: Boolean
 }
 
+
+/// The possible delimiters for an ARN resource segment.
+@private
+enum ResourceDelimiter {
+    /// The `/` character.
+    FORWARD_SLASH = "/"
+
+    /// The `:` character.
+    COLON = ":"
+}
+
 /// A string representing a service's ARN namespace.
 @pattern("^[a-z0-9.\\-]{1,63}$")
 @private
diff --git a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/DeconflictingStrategy.java b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/DeconflictingStrategy.java
index 09f84243f..cb0f47e25 100644
--- a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/DeconflictingStrategy.java
+++ b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/DeconflictingStrategy.java
@@ -17,6 +17,7 @@ package software.amazon.smithy.jsonschema;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.TreeSet;
 import java.util.function.Predicate;
 import java.util.logging.Logger;
 import java.util.regex.Pattern;
@@ -60,24 +61,30 @@ final class DeconflictingStrategy implements RefStrategy {
         // to make the result deterministic. Eliminate trait definitions
         // ahead of that computation to eliminate potential conflicts with
         // shapes that won't go into JSON Schema anyway.
-        Model scrubbedModel = ModelTransformer.create().scrubTraitDefinitions(model);
-        scrubbedModel.shapes()
-                .filter(shapePredicate.and(FunctionalUtils.not(this::isIgnoredShape)))
-                .sorted()
-                .forEach(shape -> {
+        Model scrubbedModel = null;
+        for (Shape shape : new TreeSet<>(model.toSet())) {
+            if (!shapePredicate.and(FunctionalUtils.not(this::isIgnoredShape)).test(shape)) {
+                continue;
+            }
+
             String pointer = delegate.toPointer(shape.getId());
             if (!reversePointers.containsKey(pointer)) {
                 pointers.put(shape.getId(), pointer);
                 reversePointers.put(pointer, shape.getId());
             } else {
-                String deconflictedPointer = deconflict(shape, pointer, reversePointers);
-                LOGGER.info(() -> String.format(
-                        "De-conflicted `%s` JSON schema pointer from `%s` to `%s`",
-                        shape.getId(), pointer, deconflictedPointer));
-                pointers.put(shape.getId(), deconflictedPointer);
-                reversePointers.put(deconflictedPointer, shape.getId());
+                if (scrubbedModel == null) {
+                    scrubbedModel = ModelTransformer.create().scrubTraitDefinitions(model);
+                }
+                if (scrubbedModel.getShape(reversePointers.get(pointer)).isPresent()) {
+                    String deconflictedPointer = deconflict(shape, pointer, reversePointers);
+                    LOGGER.info(() -> String.format(
+                            "De-conflicted `%s` JSON schema pointer from `%s` to `%s`",
+                            shape.getId(), pointer, deconflictedPointer));
+                    pointers.put(shape.getId(), deconflictedPointer);
+                    reversePointers.put(deconflictedPointer, shape.getId());
+                }
             }
-        });
+        }
     }
 
     // Some shapes aren't converted to JSON schema at all because they
diff --git a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/DefaultRefStrategy.java b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/DefaultRefStrategy.java
index f082670b8..0ea8d54e4 100644
--- a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/DefaultRefStrategy.java
+++ b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/DefaultRefStrategy.java
@@ -148,6 +148,17 @@ final class DefaultRefStrategy implements RefStrategy {
             return true;
         }
 
+        // Maps are not inlined by default, but can be if configured.
+        // Maps are usually not a generated type in programming languages,
+        // but JSON schema represents them as "object" types which code
+        // generators may not distinguish from converted structures.
+        //
+        // Some code generators, however, will treat inline "object" types
+        // as maps and referenced ones as structures.
+        if (shape.isMapShape() && config.getUseInlineMaps()) {
+            return true;
+        }
+
         // Strings with the enum trait are never inlined. This helps to ensure
         // that the name of an enum string can be round-tripped from
         // Smithy -> JSON Schema -> Smithy, helps OpenAPI code generators to
diff --git a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaConfig.java b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaConfig.java
index 6656647ec..d35a297ad 100644
--- a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaConfig.java
+++ b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaConfig.java
@@ -115,6 +115,7 @@ public class JsonSchemaConfig {
     private boolean disableDefaultValues = false;
     private boolean disableIntEnums = false;
     private boolean addReferenceDescriptions = false;
+    private boolean useInlineMaps = false;
 
     public JsonSchemaConfig() {
         nodeMapper.setWhenMissingSetter(NodeMapper.WhenMissing.IGNORE);
@@ -479,4 +480,26 @@ public class JsonSchemaConfig {
     public void setAddReferenceDescriptions(boolean addReferenceDescriptions) {
         this.addReferenceDescriptions = addReferenceDescriptions;
     }
+
+    /**
+     * Whether to inline map shapes when creating JSON Schema object types
+     * from them.
+     *
+     * <p>Defaults to {@code false}.</p>
+     *
+     * @return Whether to inline map shapes in the resulting schema
+     */
+    public boolean getUseInlineMaps() {
+        return useInlineMaps;
+    }
+
+    /**
+     * Sets whether to inline map shapes when creating JSON schema object types
+     * from them.
+     *
+     * @param useInlineMaps Whether to inline map shapes in the resulting schema.
+     */
+    public void setUseInlineMaps(boolean useInlineMaps) {
+        this.useInlineMaps = useInlineMaps;
+    }
 }
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/XmlFlattenedTraitValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/XmlFlattenedTraitValidator.java
new file mode 100644
index 000000000..6a5dc14e8
--- /dev/null
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/XmlFlattenedTraitValidator.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.model.validation.validators;
+
+import java.util.ArrayList;
+import java.util.List;
+import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.ListShape;
+import software.amazon.smithy.model.shapes.MemberShape;
+import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.traits.XmlFlattenedTrait;
+import software.amazon.smithy.model.traits.XmlNameTrait;
+import software.amazon.smithy.model.validation.AbstractValidator;
+import software.amazon.smithy.model.validation.ValidationEvent;
+
+/**
+ * Validates that xmlFlattened members aren't unintentionally ignoring the
+ * xmlName of their targets.
+ */
+public final class XmlFlattenedTraitValidator extends AbstractValidator {
+    @Override
+    public List<ValidationEvent> validate(Model model) {
+        List<ValidationEvent> events = new ArrayList<>();
+        for (MemberShape member : model.getMemberShapesWithTrait(XmlFlattenedTrait.class)) {
+            // Don't emit the event if they're being explicit about the xmlName on this member
+            if (member.hasTrait(XmlNameTrait.class)) {
+                continue;
+            }
+
+            Shape target = model.expectShape(member.getTarget());
+            if (target instanceof ListShape) {
+                ListShape targetList = (ListShape) target;
+                validateMemberTargetingList(member, targetList, events);
+            }
+        }
+        return events;
+    }
+
+    private void validateMemberTargetingList(MemberShape member, ListShape targetList, List<ValidationEvent> events) {
+        if (targetList.getMember().hasTrait(XmlNameTrait.class)) {
+            XmlNameTrait xmlName = targetList.getMember().expectTrait(XmlNameTrait.class);
+            if (!member.getMemberName().equals(xmlName.getValue())) {
+                events.add(warning(member, String.format(
+                        "Member is `@xmlFlattened`, so `@xmlName` of target's member (`%s`) has no effect."
+                        + " The flattened list elements will have the name of this member - `%s`. If this"
+                        + " is unintended, you can add `@xmlName(\"%s\")` to this member.",
+                        targetList.getMember().getId(), member.getMemberName(), xmlName.getValue())));
+            }
+        }
+    }
+}
diff --git a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
index a43408752..4b76f0194 100644
--- a/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
+++ b/smithy-model/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator
@@ -59,3 +59,4 @@ software.amazon.smithy.model.validation.validators.UnitTypeValidator
 software.amazon.smithy.model.validation.validators.UnstableTraitValidator
 software.amazon.smithy.model.validation.validators.XmlNamespaceTraitValidator
 software.amazon.smithy.model.validation.validators.TraitValidatorsValidator
+software.amazon.smithy.model.validation.validators.XmlFlattenedTraitValidator


Test patch:
diff --git a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/provider.definition.schema.v1.json b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/provider.definition.schema.v1.json
index 5a4d0ac45..6130be671 100644
--- a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/provider.definition.schema.v1.json
+++ b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/provider.definition.schema.v1.json
@@ -145,6 +145,13 @@
                     "description": "A reference to the Tags property in the schema.",
                     "$ref": "http://json-schema.org/draft-07/schema#/properties/$ref",
                     "default": "/properties/Tags"
+                },
+                "permissions": {
+                    "type": "array",
+                    "items": {
+                        "type": "string"
+                    },
+                    "additionalItems": false
                 }
             },
             "required": [
diff --git a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather-service-wide.cfn.json b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather-service-wide.cfn.json
index 15d71ee79..77455953b 100644
--- a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather-service-wide.cfn.json
+++ b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather-service-wide.cfn.json
@@ -80,10 +80,15 @@
     },
     "tagging": {
         "tagOnCreate": false,
-        "tagProperty": "Tags",
+        "tagProperty": "/properties/Tags",
         "tagUpdatable": true,
         "cloudFormationSystemTags": true,
-        "taggable": true
+        "taggable": true,
+        "permissions": [
+            "weather:ListTagsForResource",
+            "weather:TagResource",
+            "weather:UntagResource"
+        ]
     },
     "additionalProperties": false
 }
diff --git a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather.cfn.json b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather.cfn.json
index f011c04cf..782a98e8a 100644
--- a/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather.cfn.json
+++ b/smithy-aws-cloudformation/src/test/resources/software/amazon/smithy/aws/cloudformation/schema/fromsmithy/weather.cfn.json
@@ -79,10 +79,15 @@
     },
     "tagging": {
         "tagOnCreate": false,
-        "tagProperty": "Tags",
+        "tagProperty": "/properties/Tags",
         "tagUpdatable": true,
         "cloudFormationSystemTags": true,
-        "taggable": true
+        "taggable": true,
+        "permissions": [
+            "weather:ListTagsForCity",
+            "weather:TagCity",
+            "weather:UntagCity"
+        ]
     },
     "additionalProperties": false
 }
diff --git a/smithy-aws-protocol-tests/model/awsQuery/xml-lists.smithy b/smithy-aws-protocol-tests/model/awsQuery/xml-lists.smithy
index be3c89818..5d61ded7e 100644
--- a/smithy-aws-protocol-tests/model/awsQuery/xml-lists.smithy
+++ b/smithy-aws-protocol-tests/model/awsQuery/xml-lists.smithy
@@ -185,8 +185,10 @@ structure XmlListsOutput {
     @xmlName("renamed")
     renamedListMembers: RenamedListMembers,
 
+    @suppress(["XmlFlattenedTrait"])
     @xmlFlattened
     // The xmlname on the targeted list is ignored, and the member name is used.
+    // The validation that flags this is suppressed so we can test this behavior.
     flattenedList: RenamedListMembers,
 
     @xmlName("customName")
diff --git a/smithy-aws-protocol-tests/model/ec2Query/xml-lists.smithy b/smithy-aws-protocol-tests/model/ec2Query/xml-lists.smithy
index cd8f59f04..c68123881 100644
--- a/smithy-aws-protocol-tests/model/ec2Query/xml-lists.smithy
+++ b/smithy-aws-protocol-tests/model/ec2Query/xml-lists.smithy
@@ -183,8 +183,10 @@ structure XmlListsOutput {
     @xmlName("renamed")
     renamedListMembers: RenamedListMembers,
 
+    @suppress(["XmlFlattenedTrait"])
     @xmlFlattened
     // The xmlname on the targeted list is ignored, and the member name is used.
+    // The validation that flags this is suppressed so we can test this behavior.
     flattenedList: RenamedListMembers,
 
     @xmlName("customName")
diff --git a/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy b/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy
index 5d4123a56..482e8fa25 100644
--- a/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy
+++ b/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy
@@ -28,15 +28,15 @@ apply HttpPrefixHeaders @httpRequestTests([
         uri: "/HttpPrefixHeaders",
         body: "",
         headers: {
-            "X-Foo": "Foo",
-            "X-Foo-Abc": "Abc value",
-            "X-Foo-Def": "Def value",
+            "x-foo": "Foo",
+            "x-foo-abc": "Abc value",
+            "x-foo-def": "Def value",
         },
         params: {
             foo: "Foo",
             fooMap: {
-                Abc: "Abc value",
-                Def: "Def value",
+                abc: "Abc value",
+                def: "Def value",
             }
         }
     },
@@ -48,7 +48,7 @@ apply HttpPrefixHeaders @httpRequestTests([
         uri: "/HttpPrefixHeaders",
         body: "",
         headers: {
-            "X-Foo": "Foo"
+            "x-foo": "Foo"
         },
         params: {
             foo: "Foo",
@@ -65,11 +65,11 @@ apply HttpPrefixHeaders @httpRequestTests([
         body: "",
         params: {
             fooMap: {
-                Abc: ""
+                abc: ""
             }
         },
         headers: {
-            "X-Foo-Abc": ""
+            "x-foo-abc": ""
         }
         appliesTo: "client",
     },
@@ -82,15 +82,15 @@ apply HttpPrefixHeaders @httpResponseTests([
         protocol: restJson1,
         code: 200,
         headers: {
-            "X-Foo": "Foo",
-            "X-Foo-Abc": "Abc value",
-            "X-Foo-Def": "Def value",
+            "x-foo": "Foo",
+            "x-foo-abc": "Abc value",
+            "x-foo-def": "Def value",
         },
         params: {
             foo: "Foo",
             fooMap: {
-                Abc: "Abc value",
-                Def: "Def value",
+                abc: "Abc value",
+                def: "Def value",
             }
         }
     },
@@ -98,19 +98,19 @@ apply HttpPrefixHeaders @httpResponseTests([
 
 @input
 structure HttpPrefixHeadersInput {
-    @httpHeader("X-Foo")
+    @httpHeader("x-foo")
     foo: String,
 
-    @httpPrefixHeaders("X-Foo-")
+    @httpPrefixHeaders("x-foo-")
     fooMap: StringMap,
 }
 
 @output
 structure HttpPrefixHeadersOutput {
-    @httpHeader("X-Foo")
+    @httpHeader("x-foo")
     foo: String,
 
-    @httpPrefixHeaders("X-Foo-")
+    @httpPrefixHeaders("x-foo-")
     fooMap: StringMap,
 }
 
@@ -129,13 +129,13 @@ apply HttpPrefixHeadersInResponse @httpResponseTests([
         protocol: restJson1,
         code: 200,
         headers: {
-            "X-Foo": "Foo",
-            "Hello": "Hello"
+            "x-foo": "Foo",
+            "hello": "Hello"
         },
         params: {
             prefixHeaders: {
-                "X-Foo": "Foo",
-                "Hello": "Hello"
+                "x-foo": "Foo",
+                "hello": "Hello"
             }
         }
     },
diff --git a/smithy-aws-protocol-tests/model/restXml/document-lists.smithy b/smithy-aws-protocol-tests/model/restXml/document-lists.smithy
index b2dc28b99..c5ed12f0b 100644
--- a/smithy-aws-protocol-tests/model/restXml/document-lists.smithy
+++ b/smithy-aws-protocol-tests/model/restXml/document-lists.smithy
@@ -352,8 +352,10 @@ structure XmlListsInputOutput {
     @xmlName("renamed")
     renamedListMembers: RenamedListMembers,
 
+    @suppress(["XmlFlattenedTrait"])
     @xmlFlattened
     // The xmlname on the targeted list is ignored, and the member name is used.
+    // The validation that flags this is suppressed so we can test this behavior.
     flattenedList: RenamedListMembers,
 
     @xmlName("customName")
@@ -376,7 +378,10 @@ structure XmlListsInputOutput {
     @xmlName("myStructureList")
     structureList: StructureList,
 
+    @suppress(["XmlFlattenedTrait"])
     @xmlFlattened
+    // The xmlname on the targeted list is ignored, and the member name is used.
+    // The validation that flags this is suppressed so we can test this behavior.
     flattenedStructureList: StructureList
 }
 
diff --git a/smithy-aws-protocol-tests/model/restXml/http-prefix-headers.smithy b/smithy-aws-protocol-tests/model/restXml/http-prefix-headers.smithy
index a17cc658e..a69fc8d4f 100644
--- a/smithy-aws-protocol-tests/model/restXml/http-prefix-headers.smithy
+++ b/smithy-aws-protocol-tests/model/restXml/http-prefix-headers.smithy
@@ -27,15 +27,15 @@ apply HttpPrefixHeaders @httpRequestTests([
         uri: "/HttpPrefixHeaders",
         body: "",
         headers: {
-            "X-Foo": "Foo",
-            "X-Foo-Abc": "Abc value",
-            "X-Foo-Def": "Def value",
+            "x-foo": "Foo",
+            "x-foo-abc": "Abc value",
+            "x-foo-def": "Def value",
         },
         params: {
             foo: "Foo",
             fooMap: {
-                Abc: "Abc value",
-                Def: "Def value",
+                abc: "Abc value",
+                def: "Def value",
             }
         }
     },
@@ -47,7 +47,7 @@ apply HttpPrefixHeaders @httpRequestTests([
         uri: "/HttpPrefixHeaders",
         body: "",
         headers: {
-            "X-Foo": "Foo"
+            "x-foo": "Foo"
         },
         params: {
             foo: "Foo",
@@ -64,11 +64,11 @@ apply HttpPrefixHeaders @httpRequestTests([
         body: "",
         params: {
             fooMap: {
-                Abc: ""
+                abc: ""
             }
         },
         headers: {
-            "X-Foo-Abc": ""
+            "x-foo-abc": ""
         }
         appliesTo: "client",
     },
@@ -82,15 +82,15 @@ apply HttpPrefixHeaders @httpResponseTests([
         code: 200,
         body: "",
         headers: {
-            "X-Foo": "Foo",
-            "X-Foo-Abc": "Abc value",
-            "X-Foo-Def": "Def value",
+            "x-foo": "Foo",
+            "x-foo-abc": "Abc value",
+            "x-foo-def": "Def value",
         },
         params: {
             foo: "Foo",
             fooMap: {
-                Abc: "Abc value",
-                Def: "Def value",
+                abc: "Abc value",
+                def: "Def value",
             }
         }
     },
@@ -101,7 +101,7 @@ apply HttpPrefixHeaders @httpResponseTests([
         code: 200,
         body: "",
         headers: {
-            "X-Foo": "Foo"
+            "x-foo": "Foo"
         },
         params: {
             foo: "Foo",
@@ -111,10 +111,10 @@ apply HttpPrefixHeaders @httpResponseTests([
 ])
 
 structure HttpPrefixHeadersInputOutput {
-    @httpHeader("X-Foo")
+    @httpHeader("x-foo")
     foo: String,
 
-    @httpPrefixHeaders("X-Foo-")
+    @httpPrefixHeaders("x-foo-")
     fooMap: FooPrefixHeaders,
 }
 
diff --git a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ArnIndexTest.java b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ArnIndexTest.java
index 3bb0e340a..a81f146ae 100644
--- a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ArnIndexTest.java
+++ b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ArnIndexTest.java
@@ -34,7 +34,7 @@ public class ArnIndexTest {
     public static void beforeClass() {
         model = Model.assembler()
                 .discoverModels(ArnIndexTest.class.getClassLoader())
-                .addImport(ArnIndexTest.class.getResource("test-model.json"))
+                .addImport(ArnIndexTest.class.getResource("test-model.smithy"))
                 .assemble()
                 .unwrap();
     }
@@ -104,7 +104,7 @@ public class ArnIndexTest {
     public void findsEffectiveArns() {
         Model m = Model.assembler()
                 .discoverModels(ArnIndexTest.class.getClassLoader())
-                .addImport(ArnIndexTest.class.getResource("effective-arns.json"))
+                .addImport(ArnIndexTest.class.getResource("effective-arns.smithy"))
                 .assemble()
                 .unwrap();
         ArnIndex index = ArnIndex.of(m);
diff --git a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ArnReferenceTraitTest.java b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ArnReferenceTraitTest.java
index 6ec887db5..9e34455a9 100644
--- a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ArnReferenceTraitTest.java
+++ b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ArnReferenceTraitTest.java
@@ -76,7 +76,7 @@ public class ArnReferenceTraitTest {
     public void loadsFromModel() {
         Model result = Model.assembler()
                 .discoverModels(getClass().getClassLoader())
-                .addImport(getClass().getResource("test-model.json"))
+                .addImport(getClass().getResource("test-model.smithy"))
                 .assemble()
                 .unwrap();
         Shape service = result.expectShape(ShapeId.from("ns.foo#AbsoluteResourceArn"));
diff --git a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ArnTraitTest.java b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ArnTraitTest.java
index 2049ca99e..435c9a672 100644
--- a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ArnTraitTest.java
+++ b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ArnTraitTest.java
@@ -46,12 +46,15 @@ public class ArnTraitTest {
         assertThat(arnTrait.getTemplate(), equalTo("resourceName"));
         assertThat(arnTrait.isNoAccount(), is(false));
         assertThat(arnTrait.isNoRegion(), is(false));
+        assertThat(arnTrait.isAbsolute(), is(false));
         assertThat(arnTrait.getLabels(), empty());
+        assertThat(arnTrait.getResourceDelimiter().isPresent(), is(false));
+        assertThat(arnTrait.isReusable(), is(false));
     }
 
     @Test
-    public void canSetRegionAndServiceToNo() {
-        Node node = Node.parse("{\"noAccount\": true, \"noRegion\": true, \"absolute\": false, \"template\": \"foo\"}");
+    public void canSetOtherFields() {
+        Node node = Node.parse("{\"noAccount\": true, \"noRegion\": true, \"absolute\": false, \"template\": \"foo\", \"reusable\": true}");
         TraitFactory provider = TraitFactory.createServiceFactory();
         Optional<Trait> trait = provider.createTrait(ArnTrait.ID, ShapeId.from("ns.foo#foo"), node);
 
@@ -60,8 +63,24 @@ public class ArnTraitTest {
         assertThat(arnTrait.getTemplate(), equalTo("foo"));
         assertThat(arnTrait.isNoAccount(), is(true));
         assertThat(arnTrait.isNoRegion(), is(true));
+        assertThat(arnTrait.isAbsolute(), is(false));
         assertThat(arnTrait.toNode(), equalTo(node));
         assertThat(arnTrait.toBuilder().build(), equalTo(arnTrait));
+        assertThat(arnTrait.isReusable(), is(true));
+    }
+
+    @Test
+    public void canSetAbsoluteAndDelimiter() {
+        Node node = Node.parse("{\"absolute\": true, \"template\": \"foo\", \"resourceDelimiter\": \":\"}");
+        TraitFactory provider = TraitFactory.createServiceFactory();
+        Optional<Trait> trait = provider.createTrait(ArnTrait.ID, ShapeId.from("ns.foo#foo"), node);
+
+        assertTrue(trait.isPresent());
+        ArnTrait arnTrait = (ArnTrait) trait.get();
+        assertThat(arnTrait.getTemplate(), equalTo("foo"));
+        assertThat(arnTrait.toBuilder().build(), equalTo(arnTrait));
+        assertThat(arnTrait.isAbsolute(), is(true));
+        assertThat(arnTrait.getResourceDelimiter().get(), equalTo(ArnTrait.ResourceDelimiter.COLON));
     }
 
     @Test
diff --git a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ServiceTraitTest.java b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ServiceTraitTest.java
index 9743c4b96..7de95ce78 100644
--- a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ServiceTraitTest.java
+++ b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/ServiceTraitTest.java
@@ -112,7 +112,7 @@ public class ServiceTraitTest {
     public void loadsFromModel() {
         Model result = Model.assembler()
                 .discoverModels(getClass().getClassLoader())
-                .addImport(getClass().getResource("test-model.json"))
+                .addImport(getClass().getResource("test-model.smithy"))
                 .assemble()
                 .unwrap();
         ServiceShape service = result
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/effective-arns.json b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/effective-arns.json
deleted file mode 100644
index e426d1477..000000000
--- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/effective-arns.json
+++ /dev/null
@@ -1,81 +0,0 @@
-{
-    "smithy": "2.0",
-    "shapes": {
-        "ns.foo#SomeService": {
-            "type": "service",
-            "version": "2018-03-17",
-            "resources": [
-                {
-                    "target": "ns.foo#Resource1"
-                }
-            ],
-            "traits": {
-                "aws.api#service": {
-                    "sdkId": "Some Value"
-                }
-            }
-        },
-        "ns.foo#Resource1": {
-            "type": "resource",
-            "resources": [
-                {
-                    "target": "ns.foo#Resource2"
-                }
-            ],
-            "traits": {
-                "aws.api#arn": {
-                    "template": "foo"
-                }
-            }
-        },
-        "ns.foo#Resource2": {
-            "type": "resource",
-            "identifiers": {
-                "id": {
-                    "target": "smithy.api#String"
-                }
-            },
-            "operations": [
-                {
-                    "target": "ns.foo#InstanceOperation"
-                }
-            ],
-            "collectionOperations": [
-                {
-                    "target": "ns.foo#CollectionOperation"
-                }
-            ],
-            "traits": {
-                "aws.api#arn": {
-                    "template": "foo/{id}"
-                }
-            }
-        },
-        "ns.foo#InstanceOperation": {
-            "type": "operation",
-            "input": {
-                "target": "ns.foo#InstanceOperationInput"
-            }
-        },
-        "ns.foo#InstanceOperationInput": {
-            "type": "structure",
-            "members": {
-                "id": {
-                    "target": "smithy.api#String",
-                    "traits": {
-                        "smithy.api#required": {}
-                    }
-                }
-            }
-        },
-        "ns.foo#CollectionOperation": {
-            "type": "operation",
-            "input": {
-                "target": "ns.foo#CollectionOperationInput"
-            }
-        },
-        "ns.foo#CollectionOperationInput": {
-            "type": "structure"
-        }
-    }
-}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/effective-arns.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/effective-arns.smithy
new file mode 100644
index 000000000..467e01245
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/effective-arns.smithy
@@ -0,0 +1,49 @@
+$version: "2.0"
+
+namespace ns.foo
+
+use aws.api#arn
+use aws.api#service
+
+@service(sdkId: "Some Value")
+service SomeService {
+    version: "2018-03-17"
+    resources: [
+        Resource1
+    ]
+}
+
+@arn(template: "foo")
+resource Resource1 {
+    resources: [
+        Resource2
+    ]
+}
+
+@arn(template: "foo/{id}")
+resource Resource2 {
+    identifiers: { id: String }
+    operations: [
+        InstanceOperation
+    ]
+    collectionOperations: [
+        CollectionOperation
+    ]
+}
+
+operation CollectionOperation {
+    input: CollectionOperationInput
+    output: Unit
+}
+
+operation InstanceOperation {
+    input: InstanceOperationInput
+    output: Unit
+}
+
+structure CollectionOperationInput {}
+
+structure InstanceOperationInput {
+    @required
+    id: String
+}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/arn-casing.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/arn-casing.errors
new file mode 100644
index 000000000..ab77dbc31
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/arn-casing.errors
@@ -0,0 +1,1 @@
+[ERROR] ns.foo#Resource1: A `resourceDelimiter` can only be set for an `absolute` ARN. | ArnTrait
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/arn-casing.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/arn-casing.smithy
new file mode 100644
index 000000000..ba6857279
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/arn-casing.smithy
@@ -0,0 +1,49 @@
+$version: "2.0"
+
+namespace ns.foo
+
+use aws.api#arn
+use aws.api#service
+
+@service(sdkId: "Some Value")
+service SomeService {
+    version: "2018-03-17"
+    resources: [
+        Resource1
+    ]
+}
+
+@arn(template: "foo", resourceDelimiter: ":")
+resource Resource1 {
+    resources: [
+        Resource2
+    ]
+}
+
+@arn(template: "foo/{id}")
+resource Resource2 {
+    identifiers: { id: String }
+    operations: [
+        InstanceOperation
+    ]
+    collectionOperations: [
+        CollectionOperation
+    ]
+}
+
+operation CollectionOperation {
+    input: CollectionOperationInput
+    output: Unit
+}
+
+operation InstanceOperation {
+    input: InstanceOperationInput
+    output: Unit
+}
+
+structure CollectionOperationInput {}
+
+structure InstanceOperationInput {
+    @required
+    id: String
+}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.errors
index eb9615077..494564873 100644
--- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.errors
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.errors
@@ -3,3 +3,4 @@
 [WARNING] example.weather#Weather: Service marked `aws.api#TagEnabled` is missing an operation named 'UntagResource.' | ServiceTagging
 [WARNING] example.weather#City: Suggested tag property name is '[T|t]ags'. | TagResourcePropertyName
 [DANGER] example.weather#UpdateCity: Update and put resource lifecycle operations should not support updating tags because it is a privileged operation that modifies access. | TaggableResource
+[WARNING] example.weather#Weather2: Service has resources with `aws.api#taggable` applied but does not have the `aws.api#tagEnabled` trait. | TaggableResource
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.smithy
index b15aeecee..420f0c588 100644
--- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.smithy
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/tagging/tagging-warnings.smithy
@@ -19,6 +19,11 @@ service Weather {
     operations: [GetCurrentTime]
 }
 
+service Weather2 {
+    version: "2006-03-01",
+    resources: [City]
+}
+
 structure Tag {
     key: String
     value: String
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/test-model.json b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/test-model.json
deleted file mode 100644
index 0af22fe24..000000000
--- a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/test-model.json
+++ /dev/null
@@ -1,131 +0,0 @@
-{
-    "smithy": "2.0",
-    "shapes": {
-        "ns.foo#NonAwsService": {
-            "type": "service",
-            "version": "2018-03-17"
-        },
-        "ns.foo#EmptyAwsService": {
-            "type": "service",
-            "version": "2018-03-17",
-            "traits": {
-                "aws.api#service": {
-                    "sdkId": "Something Empty"
-                }
-            }
-        },
-        "ns.foo#SomeService": {
-            "type": "service",
-            "version": "2018-03-17",
-            "resources": [
-                {
-                    "target": "ns.foo#SomeResource"
-                },
-                {
-                    "target": "ns.foo#RootArnResource"
-                },
-                {
-                    "target": "ns.foo#AbsoluteResource"
-                }
-            ],
-            "traits": {
-                "aws.api#service": {
-                    "sdkId": "Some Value",
-                    "arnNamespace": "service",
-                    "cloudFormationName": "SomeService",
-                    "endpointPrefix": "some-service"
-                }
-            }
-        },
-        "ns.foo#RootArnResource": {
-            "type": "resource",
-            "traits": {
-                "aws.api#arn": {
-                    "noRegion": true,
-                    "noAccount": true,
-                    "template": "rootArnResource"
-                }
-            }
-        },
-        "ns.foo#SomeResource": {
-            "type": "resource",
-            "identifiers": {
-                "someId": {
-                    "target": "ns.foo#SomeResourceId"
-                }
-            },
-            "resources": [
-                {
-                    "target": "ns.foo#ChildResource"
-                }
-            ],
-            "traits": {
-                "aws.api#arn": {
-                    "template": "someresource/{someId}"
-                }
-            }
-        },
-        "ns.foo#ChildResource": {
-            "type": "resource",
-            "identifiers": {
-                "someId": {
-                    "target": "ns.foo#SomeResourceId"
-                },
-                "childId": {
-                    "target": "ns.foo#ChildResourceId"
-                }
-            },
-            "resources": [
-                {
-                    "target": "ns.foo#AnotherChild"
-                }
-            ],
-            "traits": {
-                "aws.api#arn": {
-                    "template": "someresource/{someId}/{childId}"
-                }
-            }
-        },
-        "ns.foo#AnotherChild": {
-            "type": "resource",
-            "identifiers": {
-                "someId": {
-                    "target": "ns.foo#SomeResourceId"
-                },
-                "childId": {
-                    "target": "ns.foo#ChildResourceId"
-                }
-            }
-        },
-        "ns.foo#AbsoluteResource": {
-            "type": "resource",
-            "identifiers": {
-                "arn": {
-                    "target": "ns.foo#AbsoluteResourceArn"
-                }
-            },
-            "traits": {
-                "aws.api#arn": {
-                    "template": "{arn}",
-                    "absolute": true
-                }
-            }
-        },
-        "ns.foo#AbsoluteResourceArn": {
-            "type": "string",
-            "traits": {
-                "aws.api#arnReference": {
-                    "type": "AWS::SomeService::AbsoluteResource",
-                    "service": "ns.foo#SomeService",
-                    "resource": "ns.foo#AbsoluteResource"
-                }
-            }
-        },
-        "ns.foo#SomeResourceId": {
-            "type": "string"
-        },
-        "ns.foo#ChildResourceId": {
-            "type": "string"
-        }
-    }
-}
diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/test-model.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/test-model.smithy
new file mode 100644
index 000000000..fd2953f25
--- /dev/null
+++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/test-model.smithy
@@ -0,0 +1,66 @@
+$version: "2.0"
+
+namespace ns.foo
+
+use aws.api#arn
+use aws.api#arnReference
+use aws.api#service
+
+@service(sdkId: "Something Empty")
+service EmptyAwsService { version: "2018-03-17" }
+
+service NonAwsService { version: "2018-03-17" }
+
+@service(
+    sdkId: "Some Value"
+    arnNamespace: "service"
+    cloudFormationName: "SomeService"
+    endpointPrefix: "some-service"
+)
+service SomeService {
+    version: "2018-03-17"
+    resources: [
+        AbsoluteResource
+        RootArnResource
+        SomeResource
+    ]
+}
+
+@arn(template: "{arn}", absolute: true)
+resource AbsoluteResource {
+    identifiers: { arn: AbsoluteResourceArn }
+}
+
+resource AnotherChild {
+    identifiers: { childId: ChildResourceId, someId: SomeResourceId }
+}
+
+@arn(template: "someresource/{someId}/{childId}")
+resource ChildResource {
+    identifiers: { childId: ChildResourceId, someId: SomeResourceId }
+    resources: [
+        AnotherChild
+    ]
+}
+
+@arn(noRegion: true, noAccount: true, template: "rootArnResource")
+resource RootArnResource {}
+
+@arn(template: "someresource/{someId}")
+resource SomeResource {
+    identifiers: { someId: SomeResourceId }
+    resources: [
+        ChildResource
+    ]
+}
+
+@arnReference(
+    type: "AWS::SomeService::AbsoluteResource"
+    service: "ns.foo#SomeService"
+    resource: "ns.foo#AbsoluteResource"
+)
+string AbsoluteResourceArn
+
+string ChildResourceId
+
+string SomeResourceId
diff --git a/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/JsonSchemaConverterTest.java b/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/JsonSchemaConverterTest.java
index 9a9a0f3a5..b3c74ad02 100644
--- a/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/JsonSchemaConverterTest.java
+++ b/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/JsonSchemaConverterTest.java
@@ -573,6 +573,32 @@ public class JsonSchemaConverterTest {
         assertThat(schema.getAdditionalProperties().get().getType().get(), equalTo("string"));
     }
 
+    @Test
+    public void supportsInlineMapPropertyNames() {
+        ShapeId shapeId = ShapeId.from("smithy.api#String");
+        StringShape string = StringShape.builder().id(shapeId).build();
+        MapShape map = MapShape.builder().id("a.b#Map").key(shapeId).value(shapeId).build();
+        StructureShape container = StructureShape.builder().id("a.b#Container").addMember("map", map.getId()).build();
+        Model model = Model.builder().addShapes(container, map, string).build();
+        JsonSchemaConfig config = new JsonSchemaConfig();
+        config.setUseInlineMaps(true);
+        SchemaDocument document = JsonSchemaConverter.builder()
+                .config(config)
+                .model(model)
+                .build()
+                .convertShape(container);
+        Schema schema = document.getRootSchema();
+
+        assertThat(schema.getProperties().containsKey("map"), equalTo(true));
+        Schema mapMember = schema.getProperties().get("map");
+        assertThat(mapMember.getRef().isPresent(), equalTo(false));
+        assertThat(mapMember.getType().get(), equalTo("object"));
+        assertTrue(mapMember.getPropertyNames().isPresent());
+        assertThat(mapMember.getPropertyNames().get().getType().get(), equalTo("string"));
+        assertTrue(mapMember.getAdditionalProperties().isPresent());
+        assertThat(mapMember.getAdditionalProperties().get().getType().get(), equalTo("string"));
+    }
+
     @Test
     public void supportsMapPatternProperties() {
         ShapeId shapeId = ShapeId.from("smithy.api#String");
@@ -596,6 +622,35 @@ public class JsonSchemaConverterTest {
         assertThat(schema.getPatternProperties().get(pattern).getType().get(), equalTo("string"));
     }
 
+    @Test
+    public void supportsInlineMapPatternProperties() {
+        ShapeId shapeId = ShapeId.from("smithy.api#String");
+        StringShape string = StringShape.builder().id(shapeId).build();
+        String pattern = "[a-z]{1,16}";
+        StringShape key = StringShape.builder().id("a.b#Key")
+                .addTrait(new PatternTrait(pattern)).build();
+        MapShape map = MapShape.builder().id("a.b#Map").key(key.getId()).value(shapeId).build();
+        StructureShape container = StructureShape.builder().id("a.b#Container").addMember("map", map.getId()).build();
+        Model model = Model.builder().addShapes(container, map, key, string).build();
+        JsonSchemaConfig config = new JsonSchemaConfig();
+        config.setUseInlineMaps(true);
+        config.setMapStrategy(JsonSchemaConfig.MapStrategy.PATTERN_PROPERTIES);
+        SchemaDocument document = JsonSchemaConverter.builder()
+                .config(config)
+                .model(model)
+                .build()
+                .convertShape(container);
+        Schema schema = document.getRootSchema();
+
+        assertThat(schema.getProperties().containsKey("map"), equalTo(true));
+        Schema mapMember = schema.getProperties().get("map");
+        assertThat(mapMember.getRef().isPresent(), equalTo(false));
+        assertThat(mapMember.getType().get(), equalTo("object"));
+        assertThat(mapMember.getPatternProperties().size(), equalTo(1));
+        assertTrue(mapMember.getPatternProperties().containsKey(pattern));
+        assertThat(mapMember.getPatternProperties().get(pattern).getType().get(), equalTo("string"));
+    }
+
     @Test
     public void supportsMapPatternPropertiesWithDefaultPattern() {
         ShapeId shapeId = ShapeId.from("smithy.api#String");
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-flattened-and-name.errors b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-flattened-and-name.errors
new file mode 100644
index 000000000..a8b044edf
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-flattened-and-name.errors
@@ -0,0 +1,1 @@
+[WARNING] smithy.example#Struct$flattenedNoXmlName: Member is `@xmlFlattened`, so `@xmlName` of target's member (`smithy.example#ListWithXmlName$member`) has no effect. The flattened list elements will have the name of this member - `flattenedNoXmlName`. If this is unintended, you can add `@xmlName("customMember")` to this member. | XmlFlattenedTrait
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-flattened-and-name.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-flattened-and-name.smithy
new file mode 100644
index 000000000..ae2080f56
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/xml-flattened-and-name.smithy
@@ -0,0 +1,43 @@
+$version: "2"
+
+namespace smithy.example
+
+structure Struct {
+    // No event because not flattened
+    notFlattened: ListWithXmlName
+
+    // Event because flattened and non-matching member name
+    @xmlFlattened
+    flattenedNoXmlName: ListWithXmlName
+
+    // No event because the member name matches the xml name
+    @xmlFlattened
+    customMember: ListWithXmlName
+
+    // No event because you're being explicit about the name to use
+    @xmlFlattened
+    @xmlName("customMember")
+    flattenedMatchingXmlName: ListWithXmlName
+
+    // No event because you're being explicit about the name to use
+    @xmlFlattened
+    @xmlName("Bar")
+    flattenedNonMatchingXmlName: ListWithXmlName
+
+    // Validator doesn't apply to maps
+    @xmlFlattened
+    flattenedMap: MapWithXmlName
+}
+
+list ListWithXmlName {
+    @xmlName("customMember")
+    member: String
+}
+
+map MapWithXmlName {
+    @xmlName("customKey")
+    key: String
+
+    @xmlName("customValue")
+    value: String
+}
diff --git a/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy b/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy
index f2d969bca..53df9a2c3 100644
--- a/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy
+++ b/smithy-protocol-tests/model/rpcv2Cbor/defaults.smithy
@@ -25,7 +25,7 @@ apply OperationWithDefaults @httpRequestTests([
             "Content-Length"
         ],
         bodyMediaType: "application/cbor"
-        body: "v2hkZWZhdWx0c79tZGVmYXVsdFN0cmluZ2JoaW5kZWZhdWx0Qm9vbGVhbvVrZGVmYXVsdExpc3Sf/3BkZWZhdWx0VGltZXN0YW1wwQBrZGVmYXVsdEJsb2JjYWJja2RlZmF1bHRCeXRlAWxkZWZhdWx0U2hvcnQBbmRlZmF1bHRJbnRlZ2VyCmtkZWZhdWx0TG9uZxhkbGRlZmF1bHRGbG9hdPo/gAAAbWRlZmF1bHREb3VibGX6P4AAAGpkZWZhdWx0TWFwv/9rZGVmYXVsdEVudW1jRk9PbmRlZmF1bHRJbnRFbnVtAWtlbXB0eVN0cmluZ2BsZmFsc2VCb29sZWFu9GllbXB0eUJsb2JgaHplcm9CeXRlAGl6ZXJvU2hvcnQAa3plcm9JbnRlZ2VyAGh6ZXJvTG9uZwBpemVyb0Zsb2F0+gAAAABqemVyb0RvdWJsZfoAAAAA//8="
+        body: "v2hkZWZhdWx0c79tZGVmYXVsdFN0cmluZ2JoaW5kZWZhdWx0Qm9vbGVhbvVrZGVmYXVsdExpc3Sf/3BkZWZhdWx0VGltZXN0YW1wwQBrZGVmYXVsdEJsb2JDYWJja2RlZmF1bHRCeXRlAWxkZWZhdWx0U2hvcnQBbmRlZmF1bHRJbnRlZ2VyCmtkZWZhdWx0TG9uZxhkbGRlZmF1bHRGbG9hdPo/gAAAbWRlZmF1bHREb3VibGX6P4AAAGpkZWZhdWx0TWFwv/9rZGVmYXVsdEVudW1jRk9PbmRlZmF1bHRJbnRFbnVtAWtlbXB0eVN0cmluZ2BsZmFsc2VCb29sZWFu9GllbXB0eUJsb2JAaHplcm9CeXRlAGl6ZXJvU2hvcnQAa3plcm9JbnRlZ2VyAGh6ZXJvTG9uZwBpemVyb0Zsb2F0+gAAAABqemVyb0RvdWJsZfoAAAAA//8"
         params: {
             defaults: {}
         }