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 fdbaae7fec9..81782ae6481 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 @@ -1053,6 +1053,58 @@ disableIntEnums (``boolean``) } +.. _generate-openapi-setting-addReferenceDescriptions: + +addReferenceDescriptions (``boolean``) + Set to ``true`` to add the ``description`` property to ``$ref`` members + with the value of the member's :ref:`documentation-trait` trait. + + .. important:: + + This property is only supported when :ref:`version ` + is set to ``3.1.0``. + + By default, ``$ref`` members will have no ``description``: + + .. code-block:: smithy + :caption: example.smithy + + structure Foo { + /// Member docs + bar: Bar + } + + .. code-block:: json + :caption: Example.openapi.json + + { + "Foo": { + "type": "object", + "properties": { + "bar": { + "$ref": "#/definitions/Bar" + } + } + } + } + + With this enabled, member docs will be added: + + .. code-block:: json + :caption: Example.openapi.json + + { + "Foo": { + "type": "object", + "properties": { + "bar": { + "$ref": "#/definitions/Bar", + "description": "Member docs" + } + } + } + } + ---------------- Security schemes ---------------- 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 6794a8c2929..6656647ec28 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 @@ -114,6 +114,7 @@ public String toString() { private boolean useIntegerType; private boolean disableDefaultValues = false; private boolean disableIntEnums = false; + private boolean addReferenceDescriptions = false; public JsonSchemaConfig() { nodeMapper.setWhenMissingSetter(NodeMapper.WhenMissing.IGNORE); @@ -456,4 +457,26 @@ public JsonSchemaVersion getJsonSchemaVersion() { public void setJsonSchemaVersion(JsonSchemaVersion schemaVersion) { this.jsonSchemaVersion = Objects.requireNonNull(schemaVersion); } + + /** + * Whether to add the {@code description} property to Schema References + * when converting Smithy member shapes into JSON Schema with the value + * of the member's documentation. + * + *

Defaults to {@code false}.

+ * + * @return Whether to add descriptions to Schema References. + */ + public boolean getAddReferenceDescriptions() { + return addReferenceDescriptions; + } + + /** + * Sets whether the {@code description} property should be added to Schema References. + * + * @param addReferenceDescriptions Whether to add descriptions to Schema References + */ + public void setAddReferenceDescriptions(boolean addReferenceDescriptions) { + this.addReferenceDescriptions = addReferenceDescriptions; + } } diff --git a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java index 4f6898ff523..a6067d9593f 100644 --- a/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java +++ b/smithy-jsonschema/src/main/java/software/amazon/smithy/jsonschema/JsonSchemaShapeVisitor.java @@ -106,6 +106,11 @@ private Schema createRef(MemberShape member) { if (member.hasTrait(DeprecatedTrait.class) && getJsonSchemaVersion() != JsonSchemaVersion.DRAFT07) { refBuilder.deprecated(true); } + + if (converter.getConfig().getAddReferenceDescriptions()) { + descriptionMessage(member).ifPresent(refBuilder::description); + } + // Wrap the ref and default in an allOf if disableDefaultValues has been not been disabled on config. if (member.hasTrait(DefaultTrait.class) && !converter.getConfig().getDisableDefaultValues()) { Schema def = Schema.builder().defaultValue(member.expectTrait(DefaultTrait.class).toNode()).build(); 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 21410679d43..9a9a0f3a535 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 @@ -887,4 +887,24 @@ public void dontAddDeprecatedTraitOnAMemberWhenOldVersion() { Schema memberSchema = document.getRootSchema().getProperties().get("member"); assertThat(memberSchema.isDeprecated(), equalTo(false)); } + + @Test + public void canAddMemberDocumentation() { + Model model = Model.assembler() + .addImport(getClass().getResource("member-documentation.smithy")) + .assemble() + .unwrap(); + + JsonSchemaConfig config = new JsonSchemaConfig(); + config.setAddReferenceDescriptions(true); + SchemaDocument document = JsonSchemaConverter.builder() + .config(config) + .model(model) + .build() + .convert(); + + Node expected = Node.parse( + IoUtils.toUtf8String(getClass().getResourceAsStream("member-documentation.jsonschema.json"))); + Node.assertEquals(document.toNode(), expected); + } } diff --git a/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/member-documentation.jsonschema.json b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/member-documentation.jsonschema.json new file mode 100644 index 00000000000..d2b622cc9b0 --- /dev/null +++ b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/member-documentation.jsonschema.json @@ -0,0 +1,20 @@ +{ + "definitions": { + "Foo": { + "type": "object", + "properties": { + "foo": { + "type": "string", + "description": "simple docs" + }, + "bar": { + "$ref": "#/definitions/Bar", + "description": "structure docs" + } + } + }, + "Bar": { + "type": "object" + } + } +} diff --git a/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/member-documentation.smithy b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/member-documentation.smithy new file mode 100644 index 00000000000..edf4215de2f --- /dev/null +++ b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/member-documentation.smithy @@ -0,0 +1,13 @@ +$version: "2.0" + +namespace smithy.example + +structure Foo { + /// simple docs + foo: String + + /// structure docs + bar: Bar +} + +structure Bar {} diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverter.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverter.java index 94b19f12c97..beddd9e4337 100644 --- a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverter.java +++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverter.java @@ -51,6 +51,7 @@ import software.amazon.smithy.model.validation.ValidationUtils; import software.amazon.smithy.openapi.OpenApiConfig; import software.amazon.smithy.openapi.OpenApiException; +import software.amazon.smithy.openapi.OpenApiVersion; import software.amazon.smithy.openapi.model.ComponentsObject; import software.amazon.smithy.openapi.model.InfoObject; import software.amazon.smithy.openapi.model.OpenApi; @@ -174,6 +175,12 @@ private ConversionEnvironment createConversionEnvironment(Model throw new OpenApiException("openapi is missing required property, `service`"); } + if (config.getAddReferenceDescriptions() && config.getVersion() == OpenApiVersion.VERSION_3_0_2) { + throw new OpenApiException( + "openapi property `addReferenceDescriptions` requires openapi version 3.1.0 or later.\n" + + "Suggestion: Add `\"version\"`: \"3.1.0\" to your openapi config."); + } + // Find the service shape. ServiceShape service = model.getShape(serviceShapeId) .orElseThrow(() -> new IllegalArgumentException(String.format( diff --git a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverterTest.java b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverterTest.java index 2c3fcdc802c..efe4a096841 100644 --- a/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverterTest.java +++ b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverterTest.java @@ -22,6 +22,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.util.Collections; import java.util.List; @@ -639,4 +640,37 @@ public void removesMixins() { Node.assertEquals(result, expectedNode); } + + @Test + public void convertsMemberDocumentation() { + Model model = Model.assembler() + .addImport(getClass().getResource("documentation-test-members.smithy")) + .discoverModels() + .assemble() + .unwrap(); + OpenApiConfig config = new OpenApiConfig(); + config.setService(ShapeId.from("smithy.example#MyDocs")); + config.setVersion(OpenApiVersion.VERSION_3_1_0); + config.setAddReferenceDescriptions(true); + Node result = OpenApiConverter.create().config(config).convertToNode(model); + Node expectedNode = Node.parse(IoUtils.toUtf8String( + getClass().getResourceAsStream("documentation-test-members.openapi.json"))); + + Node.assertEquals(result, expectedNode); + } + + @Test + public void convertingMemberDocsRequired3_1() { + Model model = Model.assembler() + .addImport(getClass().getResource("documentation-test-members.smithy")) + .discoverModels() + .assemble() + .unwrap(); + OpenApiConfig config = new OpenApiConfig(); + config.setService(ShapeId.from("smithy.example#MyDocs")); + config.setAddReferenceDescriptions(true); + OpenApiConverter converter = OpenApiConverter.create().config(config); + + assertThrows(OpenApiException.class, () -> converter.convertToNode(model)); + } } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/documentation-test-members.openapi.json b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/documentation-test-members.openapi.json new file mode 100644 index 00000000000..08f239aae4a --- /dev/null +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/documentation-test-members.openapi.json @@ -0,0 +1,55 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "MyDocs", + "version": "2018-01-01", + "description": "Service" + }, + "paths": { + "/": { + "get": { + "description": "Operation", + "operationId": "MyDocsOperation", + "responses": { + "200": { + "description": "MyDocsOperation 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MyDocsOperationResponseContent" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "MyDocsOperationResponseContent": { + "type": "object", + "description": "Output", + "properties": { + "foo": { + "type": "string", + "description": "foo member." + }, + "nested": { + "$ref": "#/components/schemas/Nested", + "description": "nested member." + } + } + }, + "Nested": { + "type": "object", + "description": "Nested", + "properties": { + "baz": { + "type": "string" + } + } + } + } + } +} diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/documentation-test-members.smithy b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/documentation-test-members.smithy new file mode 100644 index 00000000000..789f43c3864 --- /dev/null +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/documentation-test-members.smithy @@ -0,0 +1,31 @@ +$version: "2.0" + +namespace smithy.example + +/// Service +@aws.protocols#restJson1 +service MyDocs { + version: "2018-01-01", + operations: [MyDocsOperation] +} + +/// Operation +@http(method: "GET", uri: "/") +@readonly +operation MyDocsOperation { + output: Output +} + +/// Output +structure Output { + /// foo member. + foo: String, + + /// nested member. + nested: Nested, +} + +/// Nested +structure Nested { + baz: String, +}