Skip to content

Commit 494ddef

Browse files
authored
Add support for json schema oneOf enumStrategy (#2504)
This adds support for a new json schema setting, `enumStrategy`, that allows for variants `enum` (current behaviour, and default) and `oneOf` (gets converted to a oneOf with const string values, allowing for documentation of individual enum variants.)
1 parent a26b950 commit 494ddef

File tree

6 files changed

+179
-0
lines changed

6 files changed

+179
-0
lines changed

smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaConfig.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,42 @@ public String toString() {
8585
}
8686
}
8787

88+
/**
89+
* Configures how Smithy enum shapes are converted to JSON Schema
90+
*/
91+
public enum EnumStrategy {
92+
93+
/**
94+
* Converts to a schema that uses enum, which contains an array of strings
95+
*
96+
* <p>This is the default setting used if not configured.
97+
*/
98+
ENUM("enum"),
99+
100+
/**
101+
* Converts to a schema that uses oneOf, with an array of const strings and optional
102+
* descriptions for documentation purposes
103+
*/
104+
ONE_OF("oneOf");
105+
106+
private final String stringValue;
107+
108+
EnumStrategy(String stringValue) {
109+
this.stringValue = stringValue;
110+
}
111+
112+
@Override
113+
public String toString() {
114+
return stringValue;
115+
}
116+
}
117+
88118
private boolean alphanumericOnlyRefs;
89119
private boolean useJsonName;
90120
private TimestampFormatTrait.Format defaultTimestampFormat = TimestampFormatTrait.Format.DATE_TIME;
91121
private UnionStrategy unionStrategy = UnionStrategy.ONE_OF;
92122
private MapStrategy mapStrategy = MapStrategy.PROPERTY_NAMES;
123+
private EnumStrategy enumStrategy = EnumStrategy.ENUM;
93124
private String definitionPointer;
94125
private ObjectNode schemaDocumentExtensions = Node.objectNode();
95126
private ObjectNode extensions = Node.objectNode();
@@ -186,6 +217,18 @@ public void setMapStrategy(MapStrategy mapStrategy) {
186217
this.mapStrategy = mapStrategy;
187218
}
188219

220+
public EnumStrategy getEnumStrategy() {
221+
return enumStrategy;
222+
}
223+
224+
/**
225+
* Configures how Smithy enum shapes ae converted to JSON Schema
226+
* @param enumStrategy The enum strategy to use
227+
*/
228+
public void setEnumStrategy(EnumStrategy enumStrategy) {
229+
this.enumStrategy = enumStrategy;
230+
}
231+
189232
public String getDefinitionPointer() {
190233
return definitionPointer != null ? definitionPointer : jsonSchemaVersion.getDefaultDefinitionPointer();
191234
}

smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@
77
import java.util.ArrayList;
88
import java.util.Collection;
99
import java.util.List;
10+
import java.util.Map;
1011
import java.util.Optional;
1112
import java.util.Set;
1213
import java.util.regex.Pattern;
1314
import software.amazon.smithy.model.Model;
1415
import software.amazon.smithy.model.node.Node.NonNumericFloat;
16+
import software.amazon.smithy.model.node.StringNode;
1517
import software.amazon.smithy.model.shapes.BigDecimalShape;
1618
import software.amazon.smithy.model.shapes.BigIntegerShape;
1719
import software.amazon.smithy.model.shapes.BlobShape;
1820
import software.amazon.smithy.model.shapes.BooleanShape;
1921
import software.amazon.smithy.model.shapes.ByteShape;
2022
import software.amazon.smithy.model.shapes.DocumentShape;
2123
import software.amazon.smithy.model.shapes.DoubleShape;
24+
import software.amazon.smithy.model.shapes.EnumShape;
2225
import software.amazon.smithy.model.shapes.FloatShape;
2326
import software.amazon.smithy.model.shapes.IntegerShape;
2427
import software.amazon.smithy.model.shapes.ListShape;
@@ -248,6 +251,43 @@ public Schema unionShape(UnionShape shape) {
248251
}
249252
}
250253

254+
@Override
255+
public Schema enumShape(EnumShape shape) {
256+
JsonSchemaConfig.EnumStrategy enumStrategy = converter.getConfig().getEnumStrategy();
257+
258+
switch (enumStrategy) {
259+
case ENUM:
260+
return super.enumShape(shape);
261+
case ONE_OF:
262+
Map<String, String> enumValues = shape.getEnumValues();
263+
List<Schema> schemas = new ArrayList<>();
264+
265+
for (Map.Entry<String, MemberShape> entry : shape.getAllMembers().entrySet()) {
266+
String memberName = entry.getKey();
267+
MemberShape member = entry.getValue();
268+
Schema enumSchema = Schema.builder()
269+
.constValue(StringNode.from(enumValues.get(memberName)))
270+
.description(member.getTrait(DocumentationTrait.class)
271+
.map(DocumentationTrait::getValue)
272+
.orElse(null))
273+
.build();
274+
275+
schemas.add(enumSchema);
276+
}
277+
278+
return buildSchema(shape,
279+
Schema.builder()
280+
.description(shape.getTrait(DocumentationTrait.class)
281+
.map(DocumentationTrait::getValue)
282+
.orElse(null))
283+
.type("string")
284+
.oneOf(schemas));
285+
default: {
286+
throw new SmithyJsonSchemaException(String.format("Unsupported enum strategy: %s", enumStrategy));
287+
}
288+
}
289+
}
290+
251291
@Override
252292
public Schema timestampShape(TimestampShape shape) {
253293
return buildSchema(shape, createBuilder(shape, "string"));

smithy-jsonschema/src/test/java/software/amazon/smithy/jsonschema/JsonSchemaConverterTest.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,43 @@ public void supportsIntEnumsByDefault() {
817817
Node.assertEquals(document.toNode(), expected);
818818
}
819819

820+
@Test
821+
public void supportsDefaultEnumStrategy() {
822+
Model model = Model.assembler()
823+
.addImport(getClass().getResource("string-enums.smithy"))
824+
.assemble()
825+
.unwrap();
826+
827+
SchemaDocument document = JsonSchemaConverter.builder()
828+
.model(model)
829+
.build()
830+
.convert();
831+
832+
Node expected = Node.parse(
833+
IoUtils.toUtf8String(getClass().getResourceAsStream("string-enums.jsonschema.v07.json")));
834+
Node.assertEquals(document.toNode(), expected);
835+
}
836+
837+
@Test
838+
public void supportsOneOfEnumStrategy() {
839+
Model model = Model.assembler()
840+
.addImport(getClass().getResource("string-enums.smithy"))
841+
.assemble()
842+
.unwrap();
843+
844+
JsonSchemaConfig config = new JsonSchemaConfig();
845+
config.setEnumStrategy(JsonSchemaConfig.EnumStrategy.ONE_OF);
846+
SchemaDocument document = JsonSchemaConverter.builder()
847+
.model(model)
848+
.config(config)
849+
.build()
850+
.convert();
851+
852+
Node expected = Node.parse(
853+
IoUtils.toUtf8String(getClass().getResourceAsStream("string-enums-one-of.jsonschema.v07.json")));
854+
Node.assertEquals(document.toNode(), expected);
855+
}
856+
820857
@Test
821858
public void intEnumsCanBeDisabled() {
822859
Model model = Model.assembler()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"definitions": {
3+
"Foo": {
4+
"type": "object",
5+
"properties": {
6+
"bar": {
7+
"$ref": "#/definitions/TestEnum"
8+
}
9+
}
10+
},
11+
"TestEnum": {
12+
"type": "string",
13+
"description": "This is a test enum",
14+
"oneOf": [
15+
{
16+
"const": "Foo",
17+
"description": "it really does foo"
18+
},
19+
{
20+
"const": "Bar"
21+
}
22+
]
23+
}
24+
}
25+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"definitions": {
3+
"Foo": {
4+
"type": "object",
5+
"properties": {
6+
"bar": {
7+
"$ref": "#/definitions/TestEnum"
8+
}
9+
}
10+
},
11+
"TestEnum": {
12+
"type": "string",
13+
"enum": [
14+
"Foo",
15+
"Bar"
16+
],
17+
"description": "This is a test enum"
18+
}
19+
}
20+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
$version: "2.0"
2+
3+
namespace smithy.example
4+
5+
structure Foo {
6+
bar: TestEnum
7+
}
8+
9+
@documentation("This is a test enum")
10+
enum TestEnum {
11+
@documentation("it really does foo")
12+
FOO = "Foo"
13+
BAR = "Bar"
14+
}

0 commit comments

Comments
 (0)