Repository: smithy-lang/smithy
Pull request #1901 (2023-08-02 16:16:31)

Update appearance of smithy init list output
*Description of changes:*
This PR updates the appearance of the `smithy init --list` output. It also adds wrapping on descriptions that would cause the table output to exceed a fixed size of 100 characters.

Before: 
<img width="1023" alt="image" src="https://github.com/smithy-lang/smithy/assets/124718352/935afaac-3f02-453f-b083-2793e79483f0">


After: 
<img width="937" alt="image" src="https://github.com/smithy-lang/smithy/assets/124718352/7e521dcf-8b1b-405e-8790-89487c465935">


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-2.0/guides/converting-to-openapi.rst b/docs/source-2.0/guides/converting-to-openapi.rst
index 39bad112e..64b2e05c2 100644
--- a/docs/source-2.0/guides/converting-to-openapi.rst
+++ b/docs/source-2.0/guides/converting-to-openapi.rst
@@ -536,6 +536,48 @@ useIntegerType (``boolean``)
             }
         }
 
+
+.. _generate-openapi-setting-disableIntegerFormat:
+
+disableIntegerFormat (``boolean``)
+    Set to true to disable setting the ``format`` property when using the
+    "integer" type that is enabled by the :ref:`useIntegerType <generate-openapi-setting-useIntegerType>`
+    configuration setting.
+
+    .. code-block:: json
+
+        {
+            "version": "2.0",
+            "plugins": {
+                "openapi": {
+                    "service": "example.weather#Weather",
+                    "useIntegerType": true,
+                    "disableIntegerFormat": true
+                }
+            }
+        }
+
+    With this enabled (the default), the ``format`` property is set to ``int32``
+    or ``int64`` for Integer or Long shapes respectively.
+
+    .. code-block:: json
+
+        {
+            "Foo": {
+                "type": "object",
+                "properties": {
+                    "myInteger": {
+                        "type": "integer",
+                        "format": "int32"
+                    },
+                    "myLong": {
+                        "type": "integer",
+                        "format": "int64"
+                    }
+                }
+            }
+        }
+
 .. _generate-openapi-setting-onErrorStatusConflict:
 
 onErrorStatusConflict (``String``)
@@ -873,6 +915,63 @@ disableDefaultValues (``boolean``)
         }
 
 
+.. _generate-openapi-setting-disableIntEnums:
+
+disableIntEnums (``boolean``)
+    Set to true to disable setting the ``enum`` property for intEnum shapes.
+
+    .. code-block:: json
+
+        {
+            "version": "2.0",
+            "plugins": {
+                "openapi": {
+                    "service": "example.weather#Weather",
+                    "disableIntEnums": true
+                }
+            }
+        }
+
+    With this disabled, intEnum shapes will be inlined and the ``enum`` property
+    will not be set:
+
+    .. code-block:: json
+
+        {
+            "Foo": {
+                "type": "object",
+                "properties": {
+                    "bar": {
+                        "type": "number"
+                    }
+                }
+            }
+        }
+
+    With this enabled (the default), intEnum shapes will have the ``enum``
+    property set and the schema will use a ``$ref``.
+
+    .. code-block:: json
+
+        {
+            "Foo": {
+                "type": "object",
+                "properties": {
+                    "bar": {
+                        "$ref": "#/definitions/MyIntEnum"
+                    }
+                }
+            },
+            "MyIntEnum": {
+                "type": "number",
+                "enum": [
+                    1,
+                    2
+                ]
+            }
+        }
+
+
 ----------------
 Security schemes
 ----------------
diff --git a/docs/source-2.0/spec/constraint-traits.rst b/docs/source-2.0/spec/constraint-traits.rst
index 78a05d2d1..767a411c6 100644
--- a/docs/source-2.0/spec/constraint-traits.rst
+++ b/docs/source-2.0/spec/constraint-traits.rst
@@ -12,6 +12,20 @@ impact the types signatures of generated code.
        that also functions like a constraint.
 
 
+Constraint trait enforcement
+============================
+
+Constraint traits SHOULD be enforced after deserializing input. For example,
+when a server deserializes a request from a client, the server SHOULD enforce
+any defined constraint traits and reject the request if appropriate.
+
+Constraint traits SHOULD NOT be enforced when serializing shapes or when
+deserializing output. For example, when returning a response from a server
+to a client, failing to serialize a response due to a constraint trait
+violation would prevent a client from observing a state change of the server
+and would provide no real recourse for the client or server to recover.
+
+
 .. smithy-trait:: smithy.api#idRef
 .. _idref-trait:
 
diff --git a/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddDefaultConfigSettings.java b/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddDefaultConfigSettings.java
index 4c95190b5..07e4d34b2 100644
--- a/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddDefaultConfigSettings.java
+++ b/smithy-aws-apigateway-openapi/src/main/java/software/amazon/smithy/aws/apigateway/openapi/AddDefaultConfigSettings.java
@@ -23,7 +23,8 @@ import software.amazon.smithy.openapi.OpenApiConfig;
  * Disables OpenAPI and JSON Schema features not supported by API Gateway.
  *
  * <p>API Gateway does not allow characters like "_". API Gateway
- * doesn't support the "default" trait.
+ * doesn't support the "default" trait or `int32` or `int64` "format"
+ * values.
  */
 final class AddDefaultConfigSettings implements ApiGatewayMapper {
     @Override
@@ -36,5 +37,9 @@ final class AddDefaultConfigSettings implements ApiGatewayMapper {
         config.setAlphanumericOnlyRefs(true);
         config.getDisableFeatures().add("default");
         config.setDisableDefaultValues(true);
+        // If the `useIntegerType` config has been set, this assures that
+        // `int32` and `int64` formats are not set on those integer types.
+        // See https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-known-issues.html
+        config.setDisableIntegerFormat(true);
     }
 }
diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/ServiceTrait.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/ServiceTrait.java
index 7d4c43afe..d56938b5a 100644
--- a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/ServiceTrait.java
+++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/ServiceTrait.java
@@ -232,9 +232,7 @@ public final class ServiceTrait extends AbstractTrait implements ToSmithyBuilder
                     && arnNamespace.equals(os.arnNamespace)
                     && cloudFormationName.equals(os.cloudFormationName)
                     && cloudTrailEventSource.equals(os.cloudTrailEventSource)
-                    && docId == null
-                        ? os.docId == null
-                        : docId.equals(os.docId)
+                    && Objects.equals(docId, os.docId)
                     && endpointPrefix.equals(os.endpointPrefix);
         }
     }
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/ColorTheme.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/ColorTheme.java
index 5e012d005..e4aa1842c 100644
--- a/smithy-cli/src/main/java/software/amazon/smithy/cli/ColorTheme.java
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/ColorTheme.java
@@ -49,5 +49,8 @@ public final class ColorTheme {
 
     public static final Style HINT_TITLE = Style.of(Style.BRIGHT_GREEN);
 
+    public static final Style TEMPLATE_TITLE = Style.of(Style.BOLD, Style.BRIGHT_WHITE);
+    public static final Style TEMPLATE_LIST_BORDER = Style.of(Style.BRIGHT_WHITE);
+
     private ColorTheme() {}
 }
diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java
index bd4a85c22..3ef715439 100644
--- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java
+++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/InitCommand.java
@@ -41,8 +41,11 @@ import software.amazon.smithy.model.node.ObjectNode;
 import software.amazon.smithy.model.node.StringNode;
 import software.amazon.smithy.utils.IoUtils;
 import software.amazon.smithy.utils.ListUtils;
+import software.amazon.smithy.utils.StringUtils;
 
 final class InitCommand implements Command {
+    private static final int LINE_LENGTH = 100;
+    private static final String COLUMN_SEPARATOR = "  ";
     private static final String SMITHY_TEMPLATE_JSON = "smithy-templates.json";
     private static final String DEFAULT_REPOSITORY_URL = "https://github.com/smithy-lang/smithy-examples.git";
     private static final String DEFAULT_TEMPLATE_NAME = "quickstart-cli";
@@ -114,48 +117,58 @@ final class InitCommand implements Command {
 
     private String getTemplateList(ObjectNode smithyTemplatesNode, Env env) {
         int maxTemplateLength = 0;
-        int maxDocumentationLength = 0;
         Map<String, String> templates = new TreeMap<>();
-
         for (Map.Entry<StringNode, Node> entry : getTemplatesNode(smithyTemplatesNode).getMembers().entrySet()) {
             String template = entry.getKey().getValue();
             String documentation = entry.getValue()
-                .expectObjectNode()
-                .expectMember(DOCUMENTATION, String.format(
-                        "Missing expected member `%s` from `%s` object", DOCUMENTATION, template))
-                .expectStringNode()
-                .getValue();
+                    .expectObjectNode()
+                    .expectMember(DOCUMENTATION, String.format(
+                            "Missing expected member `%s` from `%s` object", DOCUMENTATION, template))
+                    .expectStringNode()
+                    .getValue();
 
             templates.put(template, documentation);
 
             maxTemplateLength = Math.max(maxTemplateLength, template.length());
-            maxDocumentationLength = Math.max(maxDocumentationLength, documentation.length());
         }
+        int maxDocLength = LINE_LENGTH - maxTemplateLength - COLUMN_SEPARATOR.length();
 
-        final String space = "   ";
+        ColorBuffer builder = ColorBuffer.of(env.colors(), new StringBuilder());
 
-        ColorBuffer builder = ColorBuffer.of(env.colors(), new StringBuilder())
-                .print(pad(NAME.toUpperCase(Locale.US), maxTemplateLength), ColorTheme.LITERAL)
-                .print(space)
-                .print(DOCUMENTATION.toUpperCase(Locale.US), ColorTheme.LITERAL)
-                .println()
-                .print(pad("", maxTemplateLength).replace(' ', '-'), ColorTheme.MUTED)
-                .print(space)
-                .print(pad("", maxDocumentationLength).replace(' ', '-'), ColorTheme.MUTED)
+        writeTemplateBorder(builder, maxTemplateLength, maxDocLength);
+        builder.print(pad(NAME.toUpperCase(Locale.US), maxTemplateLength), ColorTheme.NOTE)
+                .print(COLUMN_SEPARATOR)
+                .print(DOCUMENTATION.toUpperCase(Locale.US), ColorTheme.NOTE)
                 .println();
+        writeTemplateBorder(builder, maxTemplateLength, maxDocLength);
+
+        int offset = maxTemplateLength + COLUMN_SEPARATOR.length();
 
         for (Map.Entry<String, String> entry : templates.entrySet()) {
             String template = entry.getKey();
             String doc = entry.getValue();
-            builder.print(pad(template, maxTemplateLength))
-                    .print(space)
-                    .print(pad(doc, maxDocumentationLength))
+
+            builder.print(pad(template, maxTemplateLength), ColorTheme.TEMPLATE_TITLE)
+                    .print(COLUMN_SEPARATOR)
+                    .print(wrapDocumentation(doc, maxDocLength, offset),
+                            ColorTheme.MUTED)
                     .println();
         }
 
         return builder.toString();
     }
 
+    private static void writeTemplateBorder(ColorBuffer writer, int maxNameLength, int maxDocLength) {
+        writer.print(pad("", maxNameLength).replace(" ", "─"), ColorTheme.TEMPLATE_LIST_BORDER)
+                .print(COLUMN_SEPARATOR)
+                .print(pad("", maxDocLength).replace(" ", "─"), ColorTheme.TEMPLATE_LIST_BORDER)
+                .println();
+    }
+
+    private static String wrapDocumentation(String doc, int maxLength, int offset) {
+        return StringUtils.wrap(doc, maxLength, System.lineSeparator() + pad("", offset), false);
+    }
+
     private void cloneTemplate(Path temp, ObjectNode smithyTemplatesNode, Options options,
                                StandardOptions standardOptions, Env env)
             throws IOException, InterruptedException, URISyntaxException {
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 351cb9478..f082670b8 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
@@ -35,8 +35,8 @@ import software.amazon.smithy.model.traits.EnumTrait;
  *         JSON schema definition.
  *     </li>
  *     <li>
- *         <p>Members that target structures, unions, enums, and maps use a $ref to the
- *         targeted shape. With the exception of maps, these kinds of shapes are almost
+ *         <p>Members that target structures, unions, enums, intEnums, and maps use a $ref to
+ *         the targeted shape. With the exception of maps, these kinds of shapes are almost
  *         always generated as concrete types by code generators, so it's useful to reuse
  *         them throughout the schema. However, this means that member documentation
  *         and other member traits need to be moved in some way to the containing
@@ -157,6 +157,10 @@ final class DefaultRefStrategy implements RefStrategy {
             return false;
         }
 
+        if (shape.isIntEnumShape() && !config.getDisableIntEnums()) {
+            return false;
+        }
+
         // Simple types are always inlined unless the type has the enum trait.
         return shape instanceof SimpleShape;
     }
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 9c332b809..6794a8c29 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
@@ -113,6 +113,7 @@ public class JsonSchemaConfig {
     private boolean enableOutOfServiceReferences = false;
     private boolean useIntegerType;
     private boolean disableDefaultValues = false;
+    private boolean disableIntEnums = false;
 
     public JsonSchemaConfig() {
         nodeMapper.setWhenMissingSetter(NodeMapper.WhenMissing.IGNORE);
@@ -421,6 +422,21 @@ public class JsonSchemaConfig {
     }
 
 
+    public boolean getDisableIntEnums() {
+        return disableIntEnums;
+    }
+
+    /**
+     * Set to true to disable setting an `enum` property for intEnums. When disabled,
+     * intEnums are inlined instead of using a $ref.
+     *
+     * @param disableIntEnums True to disable setting `enum` property for intEnums.
+     */
+    public void setDisableIntEnums(boolean disableIntEnums) {
+        this.disableIntEnums = disableIntEnums;
+    }
+
+
     /**
      * JSON schema version to use when converting Smithy shapes into Json Schema.
      *
diff --git a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java
index 05a32b8b2..477bf1526 100644
--- a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java
+++ b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java
@@ -321,6 +321,10 @@ final class JsonSchemaShapeVisitor extends ShapeVisitor.Default<Schema> {
                 .map(EnumTrait::getEnumDefinitionValues)
                 .ifPresent(builder::enumValues);
 
+        if (shape.isIntEnumShape() && !converter.getConfig().getDisableIntEnums()) {
+            builder.intEnumValues(shape.asIntEnumShape().get().getEnumValues().values());
+        }
+
         if (shape.hasTrait(DefaultTrait.class) && !converter.getConfig().getDisableDefaultValues()) {
             builder.defaultValue(shape.expectTrait(DefaultTrait.class).toNode());
         }
diff --git a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/Schema.java b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/Schema.java
index ef2078a24..08a33ccf8 100644
--- a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/Schema.java
+++ b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/Schema.java
@@ -63,6 +63,7 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
     private final String ref;
     private final String type;
     private final Collection<String> enumValues;
+    private final Collection<Integer> intEnumValues;
     private final Node constValue;
     private final Node defaultValue;
 
@@ -113,6 +114,7 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
         ref = builder.ref;
         type = builder.type;
         enumValues = Collections.unmodifiableCollection(builder.enumValues);
+        intEnumValues = Collections.unmodifiableCollection(builder.intEnumValues);
         constValue = builder.constValue;
         defaultValue = builder.defaultValue;
 
@@ -174,6 +176,10 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
         return Optional.ofNullable(enumValues);
     }
 
+    public Optional<Collection<Integer>> getIntEnumValues() {
+        return Optional.ofNullable(intEnumValues);
+    }
+
     public Optional<Node> getConstValue() {
         return Optional.ofNullable(constValue);
     }
@@ -379,9 +385,20 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
             result.withMember("required", required.stream().sorted().map(Node::from).collect(ArrayNode.collect()));
         }
 
-        if (!enumValues.isEmpty()) {
-            result.withOptionalMember("enum", getEnumValues()
-                    .map(v -> v.stream().map(Node::from).collect(ArrayNode.collect())));
+        if (!enumValues.isEmpty() || !intEnumValues.isEmpty()) {
+            ArrayNode.Builder builder = ArrayNode.builder();
+            if (getIntEnumValues().isPresent()) {
+                for (Integer i : getIntEnumValues().get()) {
+                    builder.withValue(i);
+                }
+            }
+
+            if (getEnumValues().isPresent()) {
+                for (String s : getEnumValues().get()) {
+                    builder.withValue(s);
+                }
+            }
+            result.withOptionalMember("enum", builder.build().asArrayNode());
         }
 
         if (!allOf.isEmpty()) {
@@ -486,6 +503,7 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
                 .ref(ref)
                 .type(type)
                 .enumValues(enumValues)
+                .intEnumValues(intEnumValues)
                 .constValue(constValue)
                 .defaultValue(defaultValue)
 
@@ -554,6 +572,7 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
         private String ref;
         private String type;
         private Collection<String> enumValues = ListUtils.of();
+        private Collection<Integer> intEnumValues = ListUtils.of();
         private Node constValue;
         private Node defaultValue;
 
@@ -625,6 +644,11 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
             return this;
         }
 
+        public Builder intEnumValues(Collection<Integer> intEnumValues) {
+            this.intEnumValues = intEnumValues == null ? ListUtils.of() : intEnumValues;
+            return this;
+        }
+
         public Builder constValue(Node constValue) {
             this.constValue = constValue;
             return this;
@@ -858,7 +882,7 @@ public final class Schema implements ToNode, ToSmithyBuilder<Schema> {
                 case "default":
                     return this.defaultValue(null);
                 case "enum":
-                    return this.enumValues(null);
+                    return this.enumValues(null).intEnumValues(null);
                 case "multipleOf":
                     return this.multipleOf(null);
                 case "maximum":
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 c2be5c196..43f6ea6b4 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
@@ -83,6 +83,7 @@ public class OpenApiConfig extends JsonSchemaConfig {
     private Map<String, Node> jsonAdd = Collections.emptyMap();
     private List<String> externalDocs = ListUtils.of(
             "Homepage", "API Reference", "User Guide", "Developer Guide", "Reference", "Guide");
+    private boolean disableIntegerFormat = false;
     private OpenApiVersion version = OpenApiVersion.VERSION_3_0_2;
 
     public OpenApiConfig() {
@@ -321,6 +322,20 @@ public class OpenApiConfig extends JsonSchemaConfig {
         super.setJsonSchemaVersion(version.getJsonSchemaVersion());
     }
 
+
+    public boolean getDisableIntegerFormat() {
+        return this.disableIntegerFormat;
+    }
+
+    /**
+     * Set to true to disable setting the `format` property on integer types.
+     *
+     * @param disableIntegerFormat True to disable setting format on integer types.
+     */
+    public void setDisableIntegerFormat(boolean disableIntegerFormat) {
+        this.disableIntegerFormat = disableIntegerFormat;
+    }
+
     /**
      * Creates an OpenApiConfig from a Node value.
      *
diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapper.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapper.java
index 581b74865..4a407eef0 100644
--- a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapper.java
+++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapper.java
@@ -62,7 +62,8 @@ public final class OpenApiJsonSchemaMapper implements JsonSchemaMapper {
         }
 
         boolean useOpenApiIntegerType = config instanceof OpenApiConfig
-                && ((OpenApiConfig) config).getUseIntegerType();
+                && ((OpenApiConfig) config).getUseIntegerType()
+                && !((OpenApiConfig) config).getDisableIntegerFormat();
 
         // Don't overwrite an existing format setting.
         if (!builder.getFormat().isPresent()) {


Test patch:
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 1d2e870c5..9743c4b96 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
@@ -19,9 +19,9 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.instanceOf;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
-
 import java.util.Optional;
 import org.junit.jupiter.api.Test;
 import software.amazon.smithy.model.Model;
@@ -126,4 +126,22 @@ public class ServiceTraitTest {
         assertFalse(trait.getDocId().isPresent());
         assertThat(trait.resolveDocId(service), equalTo("some-value-2018-03-17"));
     }
+
+    @Test
+    public void equality() {
+        Node node1 = Node.parse("{\"sdkId\": \"Foo1\", \"arnNamespace\": \"service\", \"cloudFormationName\": \"Baz\", "
+                + "\"endpointPrefix\": \"endpoint-prefix\"}");
+
+        Node node2 = Node.parse("{\"sdkId\": \"Foo2\", \"arnNamespace\": \"service\", \"cloudFormationName\": \"Baz\", "
+                + "\"endpointPrefix\": \"endpoint-prefix\"}");
+
+        TraitFactory provider = TraitFactory.createServiceFactory();
+        Optional<Trait> trait1 = provider.createTrait(ServiceTrait.ID, ShapeId.from("ns.foo#foo1"), node1);
+        Optional<Trait> trait2 = provider.createTrait(ServiceTrait.ID, ShapeId.from("ns.foo#foo2"), node2);
+
+        ServiceTrait serviceTrait1 = (ServiceTrait) trait1.get();
+        ServiceTrait serviceTrait2 = (ServiceTrait) trait2.get();
+
+        assertNotEquals(serviceTrait1, serviceTrait2);
+    }
 }
diff --git a/smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java b/smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java
index 2667a6606..d649e8250 100644
--- a/smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java
+++ b/smithy-cli/src/it/java/software/amazon/smithy/cli/InitCommandTest.java
@@ -121,15 +121,17 @@ public class InitCommandTest {
                         .append("Invalid template `blabla`. `Smithy-Examples` provides the following templates:")
                         .append(System.lineSeparator())
                         .append(System.lineSeparator())
-                        .append("NAME                    DOCUMENTATION")
+                        .append("─────────────────────  ─────────────────────────────────────────────────────────────────────────────")
                         .append(System.lineSeparator())
-                        .append("---------------------   -----------------------------------------------------")
+                        .append("NAME                   DOCUMENTATION")
                         .append(System.lineSeparator())
-                        .append("included-file-json      Smithy Quickstart example with json file included.   ")
+                        .append("─────────────────────  ─────────────────────────────────────────────────────────────────────────────")
                         .append(System.lineSeparator())
-                        .append("included-files-gradle   Smithy Quickstart example with gradle files included.")
+                        .append("included-file-json     Smithy Quickstart example with json file included.")
                         .append(System.lineSeparator())
-                        .append("quickstart-cli          Smithy Quickstart example weather service.           ")
+                        .append("included-files-gradle  Smithy Quickstart example with gradle files included.")
+                        .append(System.lineSeparator())
+                        .append("quickstart-cli         Smithy Quickstart example weather service.")
                         .append(System.lineSeparator())
                         .toString();
 
@@ -191,17 +193,19 @@ public class InitCommandTest {
                     "init", "--list", "--url", templatesDir.toString()));
 
                 String expectedOutput = new StringBuilder()
-                    .append("NAME                    DOCUMENTATION")
-                    .append(System.lineSeparator())
-                    .append("---------------------   -----------------------------------------------------")
-                    .append(System.lineSeparator())
-                    .append("included-file-json      Smithy Quickstart example with json file included.   ")
-                    .append(System.lineSeparator())
-                    .append("included-files-gradle   Smithy Quickstart example with gradle files included.")
-                    .append(System.lineSeparator())
-                    .append("quickstart-cli          Smithy Quickstart example weather service.           ")
-                    .append(System.lineSeparator())
-                    .toString();
+                        .append("─────────────────────  ─────────────────────────────────────────────────────────────────────────────")
+                        .append(System.lineSeparator())
+                        .append("NAME                   DOCUMENTATION")
+                        .append(System.lineSeparator())
+                        .append("─────────────────────  ─────────────────────────────────────────────────────────────────────────────")
+                        .append(System.lineSeparator())
+                        .append("included-file-json     Smithy Quickstart example with json file included.")
+                        .append(System.lineSeparator())
+                        .append("included-files-gradle  Smithy Quickstart example with gradle files included.")
+                        .append(System.lineSeparator())
+                        .append("quickstart-cli         Smithy Quickstart example weather service.")
+                        .append(System.lineSeparator())
+                        .toString();
 
                 assertThat(result.getOutput(), containsString(expectedOutput));
                 assertThat(result.getExitCode(), is(0));
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 23cf2627f..f7bea5ec3 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
@@ -722,4 +722,39 @@ public class JsonSchemaConverterTest {
                 IoUtils.toUtf8String(getClass().getResourceAsStream("default-values-disabled.jsonschema.v07.json")));
         Node.assertEquals(document.toNode(), expected);
     }
+
+    @Test
+    public void supportsIntEnumsByDefault() {
+        Model model = Model.assembler()
+                .addImport(getClass().getResource("int-enums.smithy"))
+                .assemble()
+                .unwrap();
+        SchemaDocument document = JsonSchemaConverter.builder()
+                .model(model)
+                .build()
+                .convert();
+
+        Node expected = Node.parse(
+                IoUtils.toUtf8String(getClass().getResourceAsStream("int-enums.jsonschema.v07.json")));
+        Node.assertEquals(document.toNode(), expected);
+    }
+
+    @Test
+    public void intEnumsCanBeDisabled() {
+        Model model = Model.assembler()
+                .addImport(getClass().getResource("int-enums.smithy"))
+                .assemble()
+                .unwrap();
+        JsonSchemaConfig config = new JsonSchemaConfig();
+        config.setDisableIntEnums(true);
+        SchemaDocument document = JsonSchemaConverter.builder()
+                .config(config)
+                .model(model)
+                .build()
+                .convert();
+
+        Node expected = Node.parse(
+                IoUtils.toUtf8String(getClass().getResourceAsStream("int-enums-disabled.jsonschema.v07.json")));
+        Node.assertEquals(document.toNode(), expected);
+    }
 }
diff --git a/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/SchemaTest.java b/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/SchemaTest.java
index 3c218f322..e663c9fb9 100644
--- a/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/SchemaTest.java
+++ b/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/SchemaTest.java
@@ -17,6 +17,7 @@ package software.amazon.smithy.jsonschema;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.not;
 
@@ -25,6 +26,8 @@ import java.util.Optional;
 import java.util.Set;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
+import software.amazon.smithy.model.node.ArrayNode;
+import software.amazon.smithy.model.node.Node;
 import software.amazon.smithy.utils.ListUtils;
 import software.amazon.smithy.utils.SetUtils;
 
@@ -207,4 +210,18 @@ public class SchemaTest {
         assertThat(schema.getProperties().keySet(), contains("bar"));
         assertThat(schema.getRequired(), contains("bar"));
     }
+
+    @Test
+    public void mergesEnumValuesWhenConvertingToNode() {
+        Schema schema = Schema.builder()
+                .enumValues(ListUtils.of("foo", "bar"))
+                .intEnumValues(ListUtils.of(1, 2))
+                .build();
+        ArrayNode node = schema.toNode().asObjectNode().get().expectArrayMember("enum");
+        assertThat(node.getElements(), containsInAnyOrder(
+                Node.from("foo"),
+                Node.from("bar"),
+                Node.from(1),
+                Node.from(2)));
+    }
 }
diff --git a/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/int-enums-disabled.jsonschema.v07.json b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/int-enums-disabled.jsonschema.v07.json
new file mode 100644
index 000000000..08b5ac5ba
--- /dev/null
+++ b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/int-enums-disabled.jsonschema.v07.json
@@ -0,0 +1,12 @@
+{
+    "definitions": {
+        "Foo": {
+            "type": "object",
+            "properties": {
+                "bar": {
+                    "type": "number"
+                }
+            }
+        }
+    }
+}
diff --git a/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/int-enums.jsonschema.v07.json b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/int-enums.jsonschema.v07.json
new file mode 100644
index 000000000..63a9209b7
--- /dev/null
+++ b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/int-enums.jsonschema.v07.json
@@ -0,0 +1,19 @@
+{
+    "definitions": {
+        "Foo": {
+            "type": "object",
+            "properties": {
+                "bar": {
+                    "$ref": "#/definitions/TestIntEnum"
+                }
+            }
+        },
+        "TestIntEnum": {
+            "type": "number",
+            "enum": [
+                1,
+                2
+            ]
+        }
+    }
+}
diff --git a/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/int-enums.smithy b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/int-enums.smithy
new file mode 100644
index 000000000..2cfd18be0
--- /dev/null
+++ b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/int-enums.smithy
@@ -0,0 +1,12 @@
+$version: "2.0"
+
+namespace smithy.example
+
+structure Foo {
+    bar: TestIntEnum
+}
+
+intEnum TestIntEnum {
+    FOO = 1
+    BAR = 2
+}
diff --git a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapperTest.java b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapperTest.java
index 41fded1df..b1603c4b6 100644
--- a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapperTest.java
+++ b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiJsonSchemaMapperTest.java
@@ -178,6 +178,29 @@ public class OpenApiJsonSchemaMapperTest {
         assertThat(document.getRootSchema().getFormat().get(), equalTo("int64"));
     }
 
+    @Test
+    public void canDisableIntegerFormats() {
+        IntegerShape integerShape = IntegerShape.builder().id("a.b#C").build();
+        LongShape longShape = LongShape.builder().id("a.b#D").build();
+        Model model = Model.builder().addShapes(integerShape, longShape).build();
+        OpenApiConfig config = new OpenApiConfig();
+        config.setUseIntegerType(true);
+        config.setDisableIntegerFormat(true);
+        JsonSchemaConverter converter = JsonSchemaConverter.builder()
+                .addMapper(new OpenApiJsonSchemaMapper())
+                .config(config)
+                .model(model)
+                .build();
+
+        SchemaDocument integerDocument = converter.convertShape(integerShape);
+        SchemaDocument longDocument = converter.convertShape(longShape);
+
+        assertThat(integerDocument.getRootSchema().getFormat().isPresent(), equalTo(false));
+        assertThat(integerDocument.getRootSchema().getType().get(), equalTo("integer"));
+        assertThat(longDocument.getRootSchema().getFormat().isPresent(), equalTo(false));
+        assertThat(longDocument.getRootSchema().getType().get(), equalTo("integer"));
+    }
+
     @Test
     public void supportsFloatFormat() {
         FloatShape shape = FloatShape.builder().id("a.b#C").build();