Skip to content

Commit

Permalink
Add config for inline JSON Schema maps
Browse files Browse the repository at this point in the history
This commit adds the useInlineMaps setting to allow users to configure
JSON Schema conversion to inline converted map shapes instead of creating
references.
  • Loading branch information
kstich committed Nov 11, 2024
1 parent f4cdede commit d8c83fa
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,26 @@ mapStrategy (``string``)
}
}
.. _generate-openapi-jsonschema-setting-useInlineMaps:

useInlineMaps (``boolean``)
Configures Smithy to generate ``map`` shapes inline instead of as
references. This is necessary for some code generators to distinguish
between ``maps`` and ``structure`` shapes when generating.

.. code-block:: json
:caption: smithy-build.json
{
"version": "1.0",
"plugins": {
"openapi": {
"service": "example.weather#Weather",
"useInlineMaps": true
}
}
}
.. _generate-openapi-jsonschema-setting-schemaDocumentExtensions:

schemaDocumentExtensions (``Map<String, any>``)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ public boolean isInlined(Shape shape) {
return true;
}

// Maps are not inlined by default, but can be if configured.
// Maps are usually not a generated type in programming languages,
// but JSON schema represents them as "object" types which code
// generators may not distinguish from converted structures.
//
// Some code generators, however, will treat inline "object" types
// as maps and referenced ones as structures.
if (shape.isMapShape() && config.getUseInlineMaps()) {
return true;
}

// Strings with the enum trait are never inlined. This helps to ensure
// that the name of an enum string can be round-tripped from
// Smithy -> JSON Schema -> Smithy, helps OpenAPI code generators to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public String toString() {
private boolean disableDefaultValues = false;
private boolean disableIntEnums = false;
private boolean addReferenceDescriptions = false;
private boolean useInlineMaps = false;

public JsonSchemaConfig() {
nodeMapper.setWhenMissingSetter(NodeMapper.WhenMissing.IGNORE);
Expand Down Expand Up @@ -479,4 +480,26 @@ public boolean getAddReferenceDescriptions() {
public void setAddReferenceDescriptions(boolean addReferenceDescriptions) {
this.addReferenceDescriptions = addReferenceDescriptions;
}

/**
* Whether to inline map shapes when creating JSON Schema object types
* from them.
*
* <p>Defaults to {@code false}.</p>
*
* @return Whether to inline map shapes in the resulting schema
*/
public boolean getUseInlineMaps() {
return useInlineMaps;
}

/**
* Sets whether to inline map shapes when creating JSON schema object types
* from them.
*
* @param useInlineMaps Whether to inline map shapes in the resulting schema.
*/
public void setUseInlineMaps(boolean useInlineMaps) {
this.useInlineMaps = useInlineMaps;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,32 @@ public void supportsMapPropertyNames() {
assertThat(schema.getAdditionalProperties().get().getType().get(), equalTo("string"));
}

@Test
public void supportsInlineMapPropertyNames() {
ShapeId shapeId = ShapeId.from("smithy.api#String");
StringShape string = StringShape.builder().id(shapeId).build();
MapShape map = MapShape.builder().id("a.b#Map").key(shapeId).value(shapeId).build();
StructureShape container = StructureShape.builder().id("a.b#Container").addMember("map", map.getId()).build();
Model model = Model.builder().addShapes(container, map, string).build();
JsonSchemaConfig config = new JsonSchemaConfig();
config.setUseInlineMaps(true);
SchemaDocument document = JsonSchemaConverter.builder()
.config(config)
.model(model)
.build()
.convertShape(container);
Schema schema = document.getRootSchema();

assertThat(schema.getProperties().containsKey("map"), equalTo(true));
Schema mapMember = schema.getProperties().get("map");
assertThat(mapMember.getRef().isPresent(), equalTo(false));
assertThat(mapMember.getType().get(), equalTo("object"));
assertTrue(mapMember.getPropertyNames().isPresent());
assertThat(mapMember.getPropertyNames().get().getType().get(), equalTo("string"));
assertTrue(mapMember.getAdditionalProperties().isPresent());
assertThat(mapMember.getAdditionalProperties().get().getType().get(), equalTo("string"));
}

@Test
public void supportsMapPatternProperties() {
ShapeId shapeId = ShapeId.from("smithy.api#String");
Expand All @@ -596,6 +622,35 @@ public void supportsMapPatternProperties() {
assertThat(schema.getPatternProperties().get(pattern).getType().get(), equalTo("string"));
}

@Test
public void supportsInlineMapPatternProperties() {
ShapeId shapeId = ShapeId.from("smithy.api#String");
StringShape string = StringShape.builder().id(shapeId).build();
String pattern = "[a-z]{1,16}";
StringShape key = StringShape.builder().id("a.b#Key")
.addTrait(new PatternTrait(pattern)).build();
MapShape map = MapShape.builder().id("a.b#Map").key(key.getId()).value(shapeId).build();
StructureShape container = StructureShape.builder().id("a.b#Container").addMember("map", map.getId()).build();
Model model = Model.builder().addShapes(container, map, key, string).build();
JsonSchemaConfig config = new JsonSchemaConfig();
config.setUseInlineMaps(true);
config.setMapStrategy(JsonSchemaConfig.MapStrategy.PATTERN_PROPERTIES);
SchemaDocument document = JsonSchemaConverter.builder()
.config(config)
.model(model)
.build()
.convertShape(container);
Schema schema = document.getRootSchema();

assertThat(schema.getProperties().containsKey("map"), equalTo(true));
Schema mapMember = schema.getProperties().get("map");
assertThat(mapMember.getRef().isPresent(), equalTo(false));
assertThat(mapMember.getType().get(), equalTo("object"));
assertThat(mapMember.getPatternProperties().size(), equalTo(1));
assertTrue(mapMember.getPatternProperties().containsKey(pattern));
assertThat(mapMember.getPatternProperties().get(pattern).getType().get(), equalTo("string"));
}

@Test
public void supportsMapPatternPropertiesWithDefaultPattern() {
ShapeId shapeId = ShapeId.from("smithy.api#String");
Expand Down

0 comments on commit d8c83fa

Please sign in to comment.