diff --git a/docs/source-2.0/guides/model-translations/converting-to-openapi.rst b/docs/source-2.0/guides/model-translations/converting-to-openapi.rst index 81782ae6481..b9c2a48e353 100644 --- a/docs/source-2.0/guides/model-translations/converting-to-openapi.rst +++ b/docs/source-2.0/guides/model-translations/converting-to-openapi.rst @@ -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``) 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 f082670b891..0ea8d54e4c9 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 @@ -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 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 6656647ec28..d35a297adbc 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 @@ -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); @@ -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. + * + *

Defaults to {@code false}.

+ * + * @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; + } } 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 9a9a0f3a535..b3c74ad0202 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 @@ -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"); @@ -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");