From 6279f9de8f19f98b0ab8300cacf8ca39ed9cc5a4 Mon Sep 17 00:00:00 2001
From: Chase Coalwell <782571+srchase@users.noreply.github.com>
Date: Wed, 2 Aug 2023 09:32:46 -0700
Subject: [PATCH] Add intEnum support json-schema (#1898)
---
.../guides/converting-to-openapi.rst | 57 +++++++++++++++++++
.../smithy/jsonschema/DefaultRefStrategy.java | 8 ++-
.../smithy/jsonschema/JsonSchemaConfig.java | 16 ++++++
.../jsonschema/JsonSchemaShapeVisitor.java | 4 ++
.../amazon/smithy/jsonschema/Schema.java | 32 +++++++++--
.../jsonschema/JsonSchemaConverterTest.java | 35 ++++++++++++
.../amazon/smithy/jsonschema/SchemaTest.java | 17 ++++++
.../int-enums-disabled.jsonschema.v07.json | 12 ++++
.../jsonschema/int-enums.jsonschema.v07.json | 19 +++++++
.../amazon/smithy/jsonschema/int-enums.smithy | 12 ++++
10 files changed, 206 insertions(+), 6 deletions(-)
create mode 100644 smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/int-enums-disabled.jsonschema.v07.json
create mode 100644 smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/int-enums.jsonschema.v07.json
create mode 100644 smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/int-enums.smithy
diff --git a/docs/source-2.0/guides/converting-to-openapi.rst b/docs/source-2.0/guides/converting-to-openapi.rst
index 39bad112eca..eb3009ad009 100644
--- a/docs/source-2.0/guides/converting-to-openapi.rst
+++ b/docs/source-2.0/guides/converting-to-openapi.rst
@@ -873,6 +873,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/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/DefaultRefStrategy.java b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/DefaultRefStrategy.java
index 351cb9478d0..f082670b891 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 @@
* JSON schema definition.
*
*
- * 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
+ *
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 @@ public boolean isInlined(Shape shape) {
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 9c332b80915..6794a8c2929 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 String toString() {
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 void setDisableDefaultValues(boolean disableDefaultValues) {
}
+ 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 05a32b8b22a..477bf152643 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 @@ private Schema.Builder updateBuilder(Shape shape, Schema.Builder builder) {
.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 ef2078a247a..08a33ccf826 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 {
private final String ref;
private final String type;
private final Collection enumValues;
+ private final Collection intEnumValues;
private final Node constValue;
private final Node defaultValue;
@@ -113,6 +114,7 @@ private Schema(Builder builder) {
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 Optional> getEnumValues() {
return Optional.ofNullable(enumValues);
}
+ public Optional> getIntEnumValues() {
+ return Optional.ofNullable(intEnumValues);
+ }
+
public Optional getConstValue() {
return Optional.ofNullable(constValue);
}
@@ -379,9 +385,20 @@ public Node toNode() {
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 Builder toBuilder() {
.ref(ref)
.type(type)
.enumValues(enumValues)
+ .intEnumValues(intEnumValues)
.constValue(constValue)
.defaultValue(defaultValue)
@@ -554,6 +572,7 @@ public static final class Builder implements SmithyBuilder {
private String ref;
private String type;
private Collection enumValues = ListUtils.of();
+ private Collection intEnumValues = ListUtils.of();
private Node constValue;
private Node defaultValue;
@@ -625,6 +644,11 @@ public Builder enumValues(Collection enumValues) {
return this;
}
+ public Builder intEnumValues(Collection 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 Builder disableProperty(String propertyName) {
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-jsonschema/src/test/java/software/amazon/smithy/jsonschema/JsonSchemaConverterTest.java b/smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/JsonSchemaConverterTest.java
index 23cf2627fe8..f7bea5ec398 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 void defaultsCanBeDisabled() {
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 3c218f32256..e663c9fb92d 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 @@
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.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 void removingPropertiesRemovesRequiredPropertiesToo() {
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 00000000000..08b5ac5ba0c
--- /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 00000000000..63a9209b7a6
--- /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 00000000000..2cfd18be0c4
--- /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
+}