Unverified Commit 2f60a5e0 authored by John DiSanti's avatar John DiSanti Committed by GitHub
Browse files

Update top level docs for crate reorganization (#2432)

parent 51df4757
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -42,7 +42,7 @@ val DECORATORS: List<ClientCodegenDecorator> = listOf(
        SdkConfigDecorator(),
        ServiceConfigDecorator(),
        AwsPresigningDecorator(),
        AwsReadmeDecorator(),
        AwsCrateDocsDecorator(),
        HttpConnectorDecorator(),
        AwsEndpointsStdLib(),
        *PromotedBuiltInsDecorators,
+153 −61
Original line number Diff line number Diff line
@@ -11,10 +11,22 @@ import org.jsoup.nodes.TextNode
import software.amazon.smithy.model.traits.DocumentationTrait
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.core.rustlang.raw
import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.containerDocsTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.docs
import software.amazon.smithy.rust.codegen.core.rustlang.escape
import software.amazon.smithy.rust.codegen.core.rustlang.rawTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization
import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsSection
import software.amazon.smithy.rust.codegen.core.smithy.generators.ManifestCustomizations
import software.amazon.smithy.rust.codegen.core.smithy.generators.ModuleDocSection
import software.amazon.smithy.rust.codegen.core.util.getTrait
import software.amazon.smithy.rust.codegen.core.util.serviceNameOrDefault
import java.util.logging.Logger

// Use a sigil that should always be unique in the text to fix line breaks and spaces
@@ -23,9 +35,9 @@ private const val LINE_BREAK_SIGIL = "[[smithy-rs-br]]"
private const val SPACE_SIGIL = "[[smithy-rs-nbsp]]"

/**
 * Generates a README.md for each service crate for display on crates.io.
 * Generates a README.md and top-level crate documentation for each service crate for display on crates.io and docs.rs.
 */
class AwsReadmeDecorator : ClientCodegenDecorator {
class AwsCrateDocsDecorator : ClientCodegenDecorator {
    override val name: String = "AwsReadmeDecorator"
    override val order: Byte = 0

@@ -36,9 +48,39 @@ class AwsReadmeDecorator : ClientCodegenDecorator {
            emptyMap()
        }

    override fun libRsCustomizations(
        codegenContext: ClientCodegenContext,
        baseCustomizations: List<LibRsCustomization>,
    ): List<LibRsCustomization> = baseCustomizations + listOf(
        object : LibRsCustomization() {
            override fun section(section: LibRsSection): Writable = when {
                section is LibRsSection.ModuleDoc && section.subsection is ModuleDocSection.ServiceDocs -> writable {
                    // Include README contents in crate docs if they are to be generated
                    if (generateReadme(codegenContext)) {
                        AwsCrateDocGenerator(codegenContext).generateCrateDocComment()(this)
                    }
                }

                else -> emptySection
            }
        },
    )

    override fun extras(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
        if (generateReadme(codegenContext)) {
            AwsSdkReadmeGenerator().generateReadme(codegenContext, rustCrate)
            AwsCrateDocGenerator(codegenContext).generateReadme(rustCrate)
        }
    }

    override fun clientConstructionDocs(codegenContext: ClientCodegenContext, baseDocs: Writable): Writable {
        return if (generateReadme(codegenContext)) {
            writable {
                val serviceName = codegenContext.serviceShape.serviceNameOrDefault("the service")
                docs("Client for calling $serviceName.")
                AwsDocs.clientConstructionDocs(codegenContext)(this)
            }
        } else {
            baseDocs
        }
    }

@@ -46,31 +88,53 @@ class AwsReadmeDecorator : ClientCodegenDecorator {
        SdkSettings.from(codegenContext.settings).generateReadme
}

internal class AwsSdkReadmeGenerator {
internal class AwsCrateDocGenerator(private val codegenContext: ClientCodegenContext) {
    private val logger: Logger = Logger.getLogger(javaClass.name)

    internal fun generateReadme(codegenContext: ClientCodegenContext, rustCrate: RustCrate) {
        val awsConfigVersion = SdkSettings.from(codegenContext.settings).awsConfigVersion
    private val awsConfigVersion by lazy {
        SdkSettings.from(codegenContext.settings).awsConfigVersion
            ?: throw IllegalStateException("missing `awsConfigVersion` codegen setting")
        rustCrate.withFile("README.md") {
    }

    private fun RustWriter.template(asComments: Boolean, text: String, vararg args: Pair<String, Any>) =
        when (asComments) {
            true -> containerDocsTemplate(text, *args)
            else -> rawTemplate(text + "\n", *args)
        }

    private fun docText(
        includeHeader: Boolean,
        includeLicense: Boolean,
        asComments: Boolean,
    ): Writable = writable {
        val moduleName = codegenContext.settings.moduleName
        val description = normalizeDescription(
            codegenContext.moduleName,
            codegenContext.settings.getService(codegenContext.model).getTrait<DocumentationTrait>()?.value ?: "",
        )
            val moduleName = codegenContext.settings.moduleName
        val snakeCaseModuleName = moduleName.replace('-', '_')
        val shortModuleName = moduleName.removePrefix("aws-sdk-")

            raw(
        if (includeHeader) {
            template(asComments, escape("# $moduleName\n"))
        }
        template(
            asComments,
            """
                # $moduleName

            **Please Note: The SDK is currently in Developer Preview and is intended strictly for
                feedback purposes only. Do not use this SDK for production workloads.**
                """.trimIndent() +
                    "\n\n$description\n\n" +
            feedback purposes only. Do not use this SDK for production workloads.**${"\n"}
            """.trimIndent(),
        )

        if (description.isNotBlank()) {
            template(asComments, escape("$description\n"))
        }

        val compileExample = AwsDocs.canRelyOnAwsConfig(codegenContext)
        val exampleMode = if (compileExample) "no_run" else "ignore"
        template(
            asComments,
            """
                    ## Getting Started
            #### Getting Started

            > Examples are available for many services and operations, check out the
            > [examples folder in GitHub](https://github.com/awslabs/aws-sdk-rust/tree/main/examples).
@@ -88,12 +152,12 @@ internal class AwsSdkReadmeGenerator {

            Then in code, a client can be created with the following:

                    ```rust
            ```rust,$exampleMode
            use $snakeCaseModuleName as $shortModuleName;

                    #[tokio::main]
            ##[#{tokio}::main]
            async fn main() -> Result<(), $shortModuleName::Error> {
                        let config = aws_config::load_from_env().await;
                let config = #{aws_config}::load_from_env().await;
                let client = $shortModuleName::Client::new(&config);

                // ... make some calls with the client
@@ -103,22 +167,43 @@ internal class AwsSdkReadmeGenerator {
            ```

            See the [client documentation](https://docs.rs/$moduleName/latest/$snakeCaseModuleName/client/struct.Client.html)
                    for information on what calls can be made, and the inputs and outputs for each of those calls.
            for information on what calls can be made, and the inputs and outputs for each of those calls.${"\n"}
            """.trimIndent().trimStart(),
            "tokio" to CargoDependency.Tokio.toDevDependency().toType(),
            "aws_config" to when (compileExample) {
                true -> AwsCargoDependency.awsConfig(codegenContext.runtimeConfig).toDevDependency().toType()
                else -> writable { rust("aws_config") }
            },
        )

                    ## Using the SDK
        template(
            asComments,
            """
            #### Using the SDK

            Until the SDK is released, we will be adding information about using the SDK to the
            [Developer Guide](https://docs.aws.amazon.com/sdk-for-rust/latest/dg/welcome.html). Feel free to suggest
                    additional sections for the guide by opening an issue and describing what you are trying to do.
            additional sections for the guide by opening an issue and describing what you are trying to do.${"\n"}
            """.trimIndent(),
        )

                    ## Getting Help
        template(
            asComments,
            """
            #### Getting Help

            * [GitHub discussions](https://github.com/awslabs/aws-sdk-rust/discussions) - For ideas, RFCs & general questions
                    * [GitHub issues](https://github.com/awslabs/aws-sdk-rust/issues/new/choose)  For bug reports & feature requests
            * [GitHub issues](https://github.com/awslabs/aws-sdk-rust/issues/new/choose) - For bug reports & feature requests
            * [Generated Docs (latest version)](https://awslabs.github.io/aws-sdk-rust/)
                    * [Usage examples](https://github.com/awslabs/aws-sdk-rust/tree/main/examples)
            * [Usage examples](https://github.com/awslabs/aws-sdk-rust/tree/main/examples)${"\n"}
            """.trimIndent(),
        )

                    ## License
        if (includeLicense) {
            template(
                asComments,
                """
                #### License

                This project is licensed under the Apache-2.0 License.
                """.trimIndent(),
@@ -126,6 +211,13 @@ internal class AwsSdkReadmeGenerator {
        }
    }

    internal fun generateCrateDocComment(): Writable =
        docText(includeHeader = false, includeLicense = false, asComments = true)

    internal fun generateReadme(rustCrate: RustCrate) = rustCrate.withFile("README.md") {
        docText(includeHeader = true, includeLicense = true, asComments = false)(this)
    }

    /**
     * Strips HTML from the description and makes it human-readable Markdown.
     */
+82 −0
Original line number Diff line number Diff line
/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package software.amazon.smithy.rustsdk

import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.core.rustlang.Writable
import software.amazon.smithy.rust.codegen.core.rustlang.docsTemplate
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase

object AwsDocs {
    /**
     * If no `aws-config` version is provided, assume that docs referencing `aws-config` cannot be given.
     * Also, STS and SSO must NOT reference `aws-config` since that would create a circular dependency.
     */
    fun canRelyOnAwsConfig(codegenContext: ClientCodegenContext): Boolean =
        SdkSettings.from(codegenContext.settings).awsConfigVersion != null &&
            !setOf(
                ShapeId.from("com.amazonaws.sts#AWSSecurityTokenServiceV20110615"),
                ShapeId.from("com.amazonaws.sso#SWBPortalService"),
            ).contains(codegenContext.serviceShape.id)

    fun clientConstructionDocs(codegenContext: ClientCodegenContext): Writable = {
        if (canRelyOnAwsConfig(codegenContext)) {
            val crateName = codegenContext.moduleName.toSnakeCase()
            docsTemplate(
                """
                #### Constructing a `Client`

                A [`Config`] is required to construct a client. For most use cases, the [`aws-config`]
                crate should be used to automatically resolve this config using
                [`aws_config::load_from_env()`], since this will resolve an [`SdkConfig`] which can be shared
                across multiple different AWS SDK clients. This config resolution process can be customized
                by calling [`aws_config::from_env()`] instead, which returns a [`ConfigLoader`] that uses
                the [builder pattern] to customize the default config.

                In the simplest case, creating a client looks as follows:
                ```rust,no_run
                ## async fn wrapper() {
                let config = #{aws_config}::load_from_env().await;
                let client = $crateName::Client::new(&config);
                ## }
                ```

                Occasionally, SDKs may have additional service-specific that can be set on the [`Config`] that
                is absent from [`SdkConfig`], or slightly different settings for a specific client may be desired.
                The [`Config`] struct implements `From<&SdkConfig>`, so setting these specific settings can be
                done as follows:

                ```rust,no_run
                ## async fn wrapper() {
                let sdk_config = #{aws_config}::load_from_env().await;
                let config = $crateName::config::Builder::from(&sdk_config)
                ## /*
                    .some_service_specific_setting("value")
                ## */
                    .build();
                ## }
                ```

                See the [`aws-config` docs] and [`Config`] for more information on customizing configuration.

                _Note:_ Client construction is expensive due to connection thread pool initialization, and should
                be done once at application start-up.

                [`Config`]: crate::Config
                [`ConfigLoader`]: https://docs.rs/aws-config/*/aws_config/struct.ConfigLoader.html
                [`SdkConfig`]: https://docs.rs/aws-config/*/aws_config/struct.SdkConfig.html
                [`aws-config` docs]: https://docs.rs/aws-config/*
                [`aws-config`]: https://crates.io/crates/aws-config
                [`aws_config::from_env()`]: https://docs.rs/aws-config/*/aws_config/fn.from_env.html
                [`aws_config::load_from_env()`]: https://docs.rs/aws-config/*/aws_config/fn.load_from_env.html
                [builder pattern]: https://rust-lang.github.io/api-guidelines/type-safety.html##builders-enable-construction-of-complex-values-c-builder
                """.trimIndent(),
                "aws_config" to AwsCargoDependency.awsConfig(codegenContext.runtimeConfig).toDevDependency().toType(),
            )
        }
    }
}
+6 −51
Original line number Diff line number Diff line
@@ -6,13 +6,12 @@
package software.amazon.smithy.rustsdk

import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.traits.TitleTrait
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.ClientRustModule
import software.amazon.smithy.rust.codegen.client.smithy.customize.ClientCodegenDecorator
import software.amazon.smithy.rust.codegen.client.smithy.featureGatedCustomizeModule
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientCustomization
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientDocs
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientGenerics
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientSection
@@ -26,13 +25,12 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlockTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.rustlang.writable
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.RustCrate
import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization
import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsSection
import software.amazon.smithy.rust.codegen.core.util.expectTrait
import software.amazon.smithy.rust.codegen.core.util.serviceNameOrDefault
import software.amazon.smithy.rustsdk.AwsRuntimeType.defaultMiddleware

private class Types(runtimeConfig: RuntimeConfig) {
@@ -224,21 +222,8 @@ private class AwsFluentClientExtensions(types: Types) {
    }
}

private class AwsFluentClientDocs(private val codegenContext: CodegenContext) : FluentClientCustomization() {
    private val serviceName = codegenContext.serviceShape.expectTrait<TitleTrait>().value
    private val serviceShape = codegenContext.serviceShape
    private val crateName = codegenContext.moduleUseName()
    private val codegenScope =
        arrayOf("aws_config" to AwsCargoDependency.awsConfig(codegenContext.runtimeConfig).toDevDependency().toType())

    // If no `aws-config` version is provided, assume that docs referencing `aws-config` cannot be given.
    // Also, STS and SSO must NOT reference `aws-config` since that would create a circular dependency.
    private fun suppressUsageDocs(): Boolean =
        SdkSettings.from(codegenContext.settings).awsConfigVersion == null ||
            setOf(
                ShapeId.from("com.amazonaws.sts#AWSSecurityTokenServiceV20110615"),
                ShapeId.from("com.amazonaws.sso#SWBPortalService"),
            ).contains(serviceShape.id)
private class AwsFluentClientDocs(private val codegenContext: ClientCodegenContext) : FluentClientCustomization() {
    private val serviceName = codegenContext.serviceShape.serviceNameOrDefault("the service")

    override fun section(section: FluentClientSection): Writable {
        return when (section) {
@@ -250,38 +235,8 @@ private class AwsFluentClientDocs(private val codegenContext: CodegenContext) :
                    /// Client for invoking operations on $serviceName. Each operation on $serviceName is a method on this
                    /// this struct. `.send()` MUST be invoked on the generated operations to dispatch the request to the service.""",
                )
                if (!suppressUsageDocs()) {
                    rustTemplate(
                        """
                        ///
                        /// ## Examples
                        /// **Constructing a client and invoking an operation**
                        /// ```rust,no_run
                        /// ## async fn docs() {
                        ///     // create a shared configuration. This can be used & shared between multiple service clients.
                        ///     let shared_config = #{aws_config}::load_from_env().await;
                        ///     let client = $crateName::Client::new(&shared_config);
                        ///     // invoke an operation
                        ///     /* let rsp = client
                        ///         .<operation_name>().
                        ///         .<param>("some value")
                        ///         .send().await; */
                        /// ## }
                        /// ```
                        /// **Constructing a client with custom configuration**
                        /// ```rust,no_run
                        /// use #{aws_config}::retry::RetryConfig;
                        /// ## async fn docs() {
                        /// let shared_config = #{aws_config}::load_from_env().await;
                        /// let config = $crateName::config::Builder::from(&shared_config)
                        ///   .retry_config(RetryConfig::disabled())
                        ///   .build();
                        /// let client = $crateName::Client::from_conf(config);
                        /// ## }
                        """,
                        *codegenScope,
                    )
                }
                AwsDocs.clientConstructionDocs(codegenContext)(this)
                FluentClientDocs.clientUsageDocs(codegenContext)(this)
            }

            else -> emptySection
+9 −5
Original line number Diff line number Diff line
@@ -5,9 +5,13 @@

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import software.amazon.smithy.rustsdk.AwsSdkReadmeGenerator
import software.amazon.smithy.model.loader.ModelAssembler
import software.amazon.smithy.rust.codegen.client.testutil.testClientCodegenContext
import software.amazon.smithy.rustsdk.AwsCrateDocGenerator

class AwsCrateDocsDecoratorTest {
    private val codegenContext = testClientCodegenContext(ModelAssembler().assemble().unwrap())

class AwsReadmeDecoratorTest {
    @Test
    fun `it converts description HTML into Markdown`() {
        assertEquals(
@@ -18,7 +22,7 @@ class AwsReadmeDecoratorTest {

            More information [can be found here](https://example.com).
            """.trimIndent(),
            AwsSdkReadmeGenerator().normalizeDescription(
            AwsCrateDocGenerator(codegenContext).normalizeDescription(
                "",
                """
                <fullname>Some service</fullname>
@@ -44,7 +48,7 @@ class AwsReadmeDecoratorTest {

            More text.
            """.trimIndent(),
            AwsSdkReadmeGenerator().normalizeDescription(
            AwsCrateDocGenerator(codegenContext).normalizeDescription(
                "",
                """
                <p>Some text introducing a list:
@@ -81,7 +85,7 @@ class AwsReadmeDecoratorTest {

            Some trailing text.
            """.trimIndent(),
            AwsSdkReadmeGenerator().normalizeDescription(
            AwsCrateDocGenerator(codegenContext).normalizeDescription(
                "",
                """
                <p>Some text introducing a description list:
Loading