Repository: smithy-lang/smithy
Pull request #2414 (2024-10-02 16:50:59)

Expand MQTT acronym
#### Background
* What do these changes do? 
* Why are they important?

#### Testing
* How did you test these changes?

#### Links
* Links to additional context, if necessary
* Issue #, if applicable (see [here](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) for a list of keywords to use for linking issues)

---
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.



Patch:
diff --git a/docs/source-2.0/additional-specs/mqtt.rst b/docs/source-2.0/additional-specs/mqtt.rst
index 5a27e9852..ccc2bcd6e 100644
--- a/docs/source-2.0/additional-specs/mqtt.rst
+++ b/docs/source-2.0/additional-specs/mqtt.rst
@@ -5,8 +5,10 @@ MQTT Protocol Bindings
 ======================
 
 This document defines traits that bind Smithy operations, inputs, and
-outputs to the `MQTT <https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html>`_
-messaging transport protocol. In addition to traits, it defines the
+outputs to the Message Queuing Telemetry Transport
+(`MQTT <https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html>`_),
+a messaging protocol that enables communication between devices and
+applications over the internet. In addition to traits, it defines the
 requirements, limitations, and expected client behavior when decorating
 Smithy models with MQTT metadata.
 
diff --git a/docs/source-2.0/aws/aws-core.rst b/docs/source-2.0/aws/aws-core.rst
index 44b5ea644..fb961d890 100644
--- a/docs/source-2.0/aws/aws-core.rst
+++ b/docs/source-2.0/aws/aws-core.rst
@@ -932,23 +932,21 @@ The ``httpChecksum`` trait is a structure that contains the following members:
     * - requestAlgorithmMember
       - ``string``
       - Defines a top-level operation input member that is used to configure
-        request checksum behavior. The input member MUST target a string shape
-        marked with the :ref:`enum-trait`. Each value in the enum represents a
-        supported checksum algorithm. Algorithms MUST be one of the following
-        supported values: "CRC64NVME", "CRC32C", "CRC32", "SHA1", or "SHA256".
+        request checksum behavior. The input member MUST target an :ref:`enum`
+        shape. Each value in the enum represents a supported checksum
+        algorithm. Algorithms MUST be one of the following supported values:
+        "CRC64NVME", "CRC32C", "CRC32", "SHA1", or "SHA256".
     * - requestChecksumRequired
       - ``boolean``
-      - Indicates an operation requires a checksum in its HTTP request. By
-        default, the checksum used for a service is an MD5 checksum passed
-        in the Content-MD5 header. When the input member represented by the
-        ``requestAlgorithmMember`` property is set, the default behavior is
-        disabled.
+      - Indicates an operation requires a checksum in its HTTP request. If the
+        input member represented by``requestAlgorithmMember`` property is not
+        set, the client uses its default algorithm.
     * - requestValidationModeMember
       - ``string``
-      - Defines a top-level operation input member used to opt-in to
-        best-effort validation of a checksum returned in the HTTP response of
-        the operation. The input member MUST target a string shape marked with
-        the :ref:`enum-trait` that contains the value "ENABLED".
+      - Defines a top-level operation input member used to opt-in to validation
+        of a checksum returned in the HTTP response of the operation. The input
+        member MUST target an :ref:`enum` shape that contains the value
+        "ENABLED".
     * - responseAlgorithms
       - ``set<string>``
       - Defines the checksum algorithms clients SHOULD look for when validating
@@ -1027,18 +1025,33 @@ behavior, will fail validation.
 Client behavior
 ===============
 
+Supported checksum algorithms
+-----------------------------
+The following checksum algorithms MUST be supported by clients.
+    * CRC32
+    * SHA1
+    * SHA256
+
+Additionally, the following checksum algorithms SHOULD be supported by clients.
+    * CRC64NVME
+    * CRC32C
+
 HTTP request checksums
 ----------------------
 
-When a client calls an operation which has the ``httpChecksum`` trait where
-``requestChecksumRequired`` is set to ``true``, the client MUST include a
-checksum in the HTTP request.
+By default, when a client calls an operation which has the ``httpChecksum``
+trait, the client MUST include a checksum in the HTTP request, unless the
+client is configured to only include checksum when ``requestChecksumRequired``
+is set to ``true``.
 
 When a client calls an operation which has the ``httpChecksum`` trait where
 ``requestAlgorithmMember`` is set, the client MUST look up the input member
 represented by ``requestAlgorithmMember`` property. The value of this member is
 the checksum algorithm that the client MUST use to compute the request payload
-checksum.
+checksum. If the client does not support that algorithm, it MUST fail the
+request. If the user did not set a value for the input member represented by
+``requestAlgorithmMember``, the client MUST use its default algorithm and
+implicitly set that value in ``requestAlgorithmMember``.
 
 The computed checksum MUST be supplied at a resolved location as per the
 :ref:`resolving checksum location <aws.protocols#httpChecksum-trait_resolving-checksum-location>`
@@ -1049,29 +1062,35 @@ of the body. The header or trailer name to use with an algorithm is resolved as
 per the :ref:`resolving checksum name <aws.protocols#httpChecksum-trait_resolving-checksum-name>`
 section.
 
-If no ``requestAlgorithmMember`` property is set, the client MUST compute an
-MD5 checksum of the request payload and place it in the ``Content-MD5`` header.
+If no ``requestAlgorithmMember`` property is set in the trait and
+``requestChecksumRequired`` is set to ``true``, the client MUST compute an MD5
+checksum of the request payload and place it in the ``Content-MD5`` header.
 
 If the HTTP header corresponding to a checksum is set explicitly, the client
 MUST use the explicitly set header and skip computing the payload checksum.
 
-.. seealso:: See :ref:`client behavior<aws.protocols#httpChecksum-trait_header-conflict-behavior>`
-    for more details.
-
 HTTP response checksums
 -----------------------
 
+When a client calls an operation which has the ``httpChecksum`` trait, the
+client MUST look up the input member represented by the
+``requestValidationModeMember`` property. If the user did not set a value for
+this member, by default the client MUST implicitly set it to ``ENABLED``,
+unless the client is configured to opt-out of this default behavior.
+
 When a client receives a response for an operation which has the ``httpChecksum``
 trait where the ``requestValidationModeMember`` property is set, the client
 MUST look up the input member represented by the property's value. If the input
-member is set to ``ENABLED``, the client MUST perform best-effort validation of
-checksum values returned in the HTTP response.
+member is set to ``ENABLED``, the client MUST perform validation of checksum
+returned in the HTTP response.
 
 A client MUST use the list of supported algorithms modeled in the
-``responseAlgorithms`` property to look up the checksum(s) for which validation
-MUST be performed. The client MUST look for the HTTP header in the response as
-per the :ref:`resolving checksum name <aws.protocols#httpChecksum-trait_resolving-checksum-name>`
-section.
+``responseAlgorithms`` property and filter it for the algorithms supported by
+the client to look up the checksum(s) for which validation MUST be performed.
+The client MUST look for the HTTP header in the response as per the
+:ref:`resolving checksum name <aws.protocols#httpChecksum-trait_resolving-checksum-name>`
+section. From the available headers in the response, the client MUST pick only
+one checksum to validate.
 
 A client MUST raise an error if the response checksum to validate does not
 match computed checksum of the response payload for the same checksum algorithm.
@@ -1098,7 +1117,12 @@ property are set, the service MUST validate an HTTP request checksum.
 When a service receives a request where the ``requestAlgorithmMember`` is set,
 the service MUST look up the input member represented by the checksum
 ``requestAlgorithmMember`` property. The value of this member is the checksum
-algorithm that the service MUST use to compute a checksum of the request payload.
+algorithm that the service MUST use to compute a checksum of the request
+payload. If the value of this member is not set, the service MUST look for the
+HTTP headers in the request as per the
+:ref:`resolving checksum name <aws.protocols#httpChecksum-trait_resolving-checksum-name>`
+section. From the available headers in the request, the service MUST pick only
+one checksum to validate.
 
 The computed checksum MUST be validated against the checksum supplied at a
 resolved location as per the :ref:`resolving checksum location
@@ -1114,7 +1138,7 @@ header.
 When using the ``httpChecksum`` trait, services MUST always accept checksum
 values in HTTP headers. For operations with streaming payload input, services
 MUST additionally accept checksum values sent in the `chunked trailer part`_ of
-the request body. Service MUST only send response checksums in HTTP headers.
+the request body.
 
 HTTP response checksums
 -----------------------
diff --git a/docs/source-2.0/guides/building-codegen/overview-and-concepts.rst b/docs/source-2.0/guides/building-codegen/overview-and-concepts.rst
index d62ae640d..79ad20ecb 100644
--- a/docs/source-2.0/guides/building-codegen/overview-and-concepts.rst
+++ b/docs/source-2.0/guides/building-codegen/overview-and-concepts.rst
@@ -75,7 +75,7 @@ choices were made and leave a record for future contributors.
 
 Example Smithy codegen design documents:
 
-- https://awslabs.github.io/smithy-rs/design/
+- https://smithy-lang.github.io/smithy-rs/design/
 - https://github.com/awslabs/smithy-kotlin/tree/main/docs/design
 - https://github.com/awslabs/aws-sdk-kotlin/tree/main/docs/design
 - https://github.com/awslabs/smithy-ruby/wiki
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 fdbaae7fe..81782ae64 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 <generate-openapi-setting-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 6794a8c29..6656647ec 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 class JsonSchemaConfig {
     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 class JsonSchemaConfig {
     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.
+     *
+     * <p>Defaults to {@code false}.</p>
+     *
+     * @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 4f6898ff5..a6067d959 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 @@ final class JsonSchemaShapeVisitor extends ShapeVisitor.Default<Schema> {
             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-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 94b19f12c..beddd9e43 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.transform.ModelTransformer;
 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 @@ public final class OpenApiConverter {
             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(


Test patch:
diff --git a/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy b/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy
index 8c279b6aa..5d4123a56 100644
--- a/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy
+++ b/smithy-aws-protocol-tests/model/restJson1/http-prefix-headers.smithy
@@ -42,7 +42,7 @@ apply HttpPrefixHeaders @httpRequestTests([
     },
     {
         id: "RestJsonHttpPrefixHeadersAreNotPresent",
-        documentation: "No prefix headers are serialized because the value is empty",
+        documentation: "No prefix headers are serialized because the value is not present",
         protocol: restJson1,
         method: "GET",
         uri: "/HttpPrefixHeaders",
@@ -56,6 +56,23 @@ apply HttpPrefixHeaders @httpRequestTests([
         },
         appliesTo: "client"
     },
+    {
+        id: "RestJsonHttpPrefixEmptyHeaders",
+        documentation: "Serialize prefix headers were the value is present but empty"
+        protocol: restJson1,
+        method: "GET",
+        uri: "/HttpPrefixHeaders",
+        body: "",
+        params: {
+            fooMap: {
+                Abc: ""
+            }
+        },
+        headers: {
+            "X-Foo-Abc": ""
+        }
+        appliesTo: "client",
+    },
 ])
 
 apply HttpPrefixHeaders @httpResponseTests([
diff --git a/smithy-aws-protocol-tests/model/restXml/http-prefix-headers.smithy b/smithy-aws-protocol-tests/model/restXml/http-prefix-headers.smithy
index eb99631c5..a17cc658e 100644
--- a/smithy-aws-protocol-tests/model/restXml/http-prefix-headers.smithy
+++ b/smithy-aws-protocol-tests/model/restXml/http-prefix-headers.smithy
@@ -41,7 +41,7 @@ apply HttpPrefixHeaders @httpRequestTests([
     },
     {
         id: "HttpPrefixHeadersAreNotPresent",
-        documentation: "No prefix headers are serialized because the value is empty",
+        documentation: "No prefix headers are serialized because the value is not present",
         protocol: restXml,
         method: "GET",
         uri: "/HttpPrefixHeaders",
@@ -55,6 +55,23 @@ apply HttpPrefixHeaders @httpRequestTests([
         },
         appliesTo: "client"
     },
+    {
+        id: "HttpPrefixEmptyHeaders",
+        documentation: "Serialize prefix headers were the value is present but empty"
+        protocol: restXml,
+        method: "GET",
+        uri: "/HttpPrefixHeaders",
+        body: "",
+        params: {
+            fooMap: {
+                Abc: ""
+            }
+        },
+        headers: {
+            "X-Foo-Abc": ""
+        }
+        appliesTo: "client",
+    },
 ])
 
 apply HttpPrefixHeaders @httpResponseTests([
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 21410679d..9a9a0f3a5 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 class JsonSchemaConverterTest {
         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 000000000..d2b622cc9
--- /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 000000000..edf4215de
--- /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/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverterTest.java b/smithy-openapi/src/test/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverterTest.java
index 2c3fcdc80..efe4a0968 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.empty;
 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 class OpenApiConverterTest {
 
         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 000000000..08f239aae
--- /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 000000000..789f43c38
--- /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,
+}