Skip to content
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

Add support for json schema oneOf enumStrategy #2504

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,42 @@ public String toString() {
}
}

/**
* Configures how Smithy enum shapes are converted to JSON Schema
*/
public enum EnumStrategy {

/**
* Converts to a schema that uses enum, which contains an array of strings
*
* <p>This is the default setting used if not configured.
*/
ENUM("enum"),

/**
* Converts to a schema that uses oneOf, with an array of const strings and optional
* descriptions for documentation purposes
*/
ONE_OF("oneOf");

private final String stringValue;

EnumStrategy(String stringValue) {
this.stringValue = stringValue;
}

@Override
public String toString() {
return stringValue;
}
}

private boolean alphanumericOnlyRefs;
private boolean useJsonName;
private TimestampFormatTrait.Format defaultTimestampFormat = TimestampFormatTrait.Format.DATE_TIME;
private UnionStrategy unionStrategy = UnionStrategy.ONE_OF;
private MapStrategy mapStrategy = MapStrategy.PROPERTY_NAMES;
private EnumStrategy enumStrategy = EnumStrategy.ENUM;
private String definitionPointer;
private ObjectNode schemaDocumentExtensions = Node.objectNode();
private ObjectNode extensions = Node.objectNode();
Expand Down Expand Up @@ -186,6 +217,18 @@ public void setMapStrategy(MapStrategy mapStrategy) {
this.mapStrategy = mapStrategy;
}

public EnumStrategy getEnumStrategy() {
return enumStrategy;
}

/**
* Configures how Smithy enum shapes ae converted to JSON Schema
* @param enumStrategy The enum strategy to use
*/
public void setEnumStrategy(EnumStrategy enumStrategy) {
this.enumStrategy = enumStrategy;
}

public String getDefinitionPointer() {
return definitionPointer != null ? definitionPointer : jsonSchemaVersion.getDefaultDefinitionPointer();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.node.Node.NonNumericFloat;
import software.amazon.smithy.model.node.StringNode;
import software.amazon.smithy.model.shapes.BigDecimalShape;
import software.amazon.smithy.model.shapes.BigIntegerShape;
import software.amazon.smithy.model.shapes.BlobShape;
import software.amazon.smithy.model.shapes.BooleanShape;
import software.amazon.smithy.model.shapes.ByteShape;
import software.amazon.smithy.model.shapes.DocumentShape;
import software.amazon.smithy.model.shapes.DoubleShape;
import software.amazon.smithy.model.shapes.EnumShape;
import software.amazon.smithy.model.shapes.FloatShape;
import software.amazon.smithy.model.shapes.IntegerShape;
import software.amazon.smithy.model.shapes.ListShape;
Expand Down Expand Up @@ -248,6 +251,43 @@ public Schema unionShape(UnionShape shape) {
}
}

@Override
public Schema enumShape(EnumShape shape) {
JsonSchemaConfig.EnumStrategy enumStrategy = converter.getConfig().getEnumStrategy();

switch (enumStrategy) {
case ENUM:
return super.enumShape(shape);
case ONE_OF:
Map<String, String> enumValues = shape.getEnumValues();
List<Schema> schemas = new ArrayList<>();

for (Map.Entry<String, MemberShape> entry : shape.getAllMembers().entrySet()) {
String memberName = entry.getKey();
MemberShape member = entry.getValue();
Schema enumSchema = Schema.builder()
.constValue(StringNode.from(enumValues.get(memberName)))
.description(member.getTrait(DocumentationTrait.class)
.map(DocumentationTrait::getValue)
.orElse(null))
.build();

schemas.add(enumSchema);
}

return buildSchema(shape,
Schema.builder()
.description(shape.getTrait(DocumentationTrait.class)
.map(DocumentationTrait::getValue)
.orElse(null))
.type("string")
.oneOf(schemas));
default: {
throw new SmithyJsonSchemaException(String.format("Unsupported enum strategy: %s", enumStrategy));
}
}
}

@Override
public Schema timestampShape(TimestampShape shape) {
return buildSchema(shape, createBuilder(shape, "string"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,43 @@ public void supportsIntEnumsByDefault() {
Node.assertEquals(document.toNode(), expected);
}

@Test
public void supportsDefaultEnumStrategy() {
Model model = Model.assembler()
.addImport(getClass().getResource("string-enums.smithy"))
.assemble()
.unwrap();

SchemaDocument document = JsonSchemaConverter.builder()
.model(model)
.build()
.convert();

Node expected = Node.parse(
IoUtils.toUtf8String(getClass().getResourceAsStream("string-enums.jsonschema.v07.json")));
Node.assertEquals(document.toNode(), expected);
}

@Test
public void supportsOneOfEnumStrategy() {
Model model = Model.assembler()
.addImport(getClass().getResource("string-enums.smithy"))
.assemble()
.unwrap();

JsonSchemaConfig config = new JsonSchemaConfig();
config.setEnumStrategy(JsonSchemaConfig.EnumStrategy.ONE_OF);
SchemaDocument document = JsonSchemaConverter.builder()
.model(model)
.config(config)
.build()
.convert();

Node expected = Node.parse(
IoUtils.toUtf8String(getClass().getResourceAsStream("string-enums-one-of.jsonschema.v07.json")));
Node.assertEquals(document.toNode(), expected);
}

@Test
public void intEnumsCanBeDisabled() {
Model model = Model.assembler()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"definitions": {
"Foo": {
"type": "object",
"properties": {
"bar": {
"$ref": "#/definitions/TestEnum"
}
}
},
"TestEnum": {
"type": "string",
"description": "This is a test enum",
"oneOf": [
{
"const": "Foo",
"description": "it really does foo"
},
{
"const": "Bar"
}
]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"definitions": {
"Foo": {
"type": "object",
"properties": {
"bar": {
"$ref": "#/definitions/TestEnum"
}
}
},
"TestEnum": {
"type": "string",
"enum": [
"Foo",
"Bar"
],
"description": "This is a test enum"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
$version: "2.0"

namespace smithy.example

structure Foo {
bar: TestEnum
}

@documentation("This is a test enum")
enum TestEnum {
@documentation("it really does foo")
FOO = "Foo"
BAR = "Bar"
}
Loading