Skip to content

Add intEnum support for json-schema #1898

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions docs/source-2.0/guides/converting-to-openapi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@
* 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
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -174,6 +176,10 @@ public Optional<Collection<String>> getEnumValues() {
return Optional.ofNullable(enumValues);
}

public Optional<Collection<Integer>> getIntEnumValues() {
return Optional.ofNullable(intEnumValues);
}

public Optional<Node> getConstValue() {
return Optional.ofNullable(constValue);
}
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -486,6 +503,7 @@ public Builder toBuilder() {
.ref(ref)
.type(type)
.enumValues(enumValues)
.intEnumValues(intEnumValues)
.constValue(constValue)
.defaultValue(defaultValue)

Expand Down Expand Up @@ -554,6 +572,7 @@ public static final class Builder implements SmithyBuilder<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;

Expand Down Expand Up @@ -625,6 +644,11 @@ public Builder enumValues(Collection<String> enumValues) {
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;
Expand Down Expand Up @@ -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":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;

Expand Down Expand Up @@ -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)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"definitions": {
"Foo": {
"type": "object",
"properties": {
"bar": {
"type": "number"
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"definitions": {
"Foo": {
"type": "object",
"properties": {
"bar": {
"$ref": "#/definitions/TestIntEnum"
}
}
},
"TestIntEnum": {
"type": "number",
"enum": [
1,
2
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
$version: "2.0"

namespace smithy.example

structure Foo {
bar: TestIntEnum
}

intEnum TestIntEnum {
FOO = 1
BAR = 2
}