Loading aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt +1 −1 Original line number Diff line number Diff line Loading @@ -42,7 +42,7 @@ val DECORATORS: List<ClientCodegenDecorator> = listOf( SdkConfigDecorator(), ServiceConfigDecorator(), AwsPresigningDecorator(), AwsReadmeDecorator(), AwsCrateDocsDecorator(), HttpConnectorDecorator(), AwsEndpointsStdLib(), *PromotedBuiltInsDecorators, Loading aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsReadmeDecorator.kt→aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCrateDocsDecorator.kt +153 −61 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 } } Loading @@ -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). Loading @@ -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 Loading @@ -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(), Loading @@ -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. */ Loading aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsDocs.kt 0 → 100644 +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(), ) } } } aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt +6 −51 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) { Loading Loading @@ -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) { Loading @@ -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 Loading aws/sdk-codegen/src/test/kotlin/AwsReadmeDecoratorTest.kt→aws/sdk-codegen/src/test/kotlin/AwsCrateDocsDecoratorTest.kt +9 −5 Original line number Diff line number Diff line Loading @@ -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( Loading @@ -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> Loading @@ -44,7 +48,7 @@ class AwsReadmeDecoratorTest { More text. """.trimIndent(), AwsSdkReadmeGenerator().normalizeDescription( AwsCrateDocGenerator(codegenContext).normalizeDescription( "", """ <p>Some text introducing a list: Loading Loading @@ -81,7 +85,7 @@ class AwsReadmeDecoratorTest { Some trailing text. """.trimIndent(), AwsSdkReadmeGenerator().normalizeDescription( AwsCrateDocGenerator(codegenContext).normalizeDescription( "", """ <p>Some text introducing a description list: Loading Loading
aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCodegenDecorator.kt +1 −1 Original line number Diff line number Diff line Loading @@ -42,7 +42,7 @@ val DECORATORS: List<ClientCodegenDecorator> = listOf( SdkConfigDecorator(), ServiceConfigDecorator(), AwsPresigningDecorator(), AwsReadmeDecorator(), AwsCrateDocsDecorator(), HttpConnectorDecorator(), AwsEndpointsStdLib(), *PromotedBuiltInsDecorators, Loading
aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsReadmeDecorator.kt→aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsCrateDocsDecorator.kt +153 −61 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 } } Loading @@ -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). Loading @@ -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 Loading @@ -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(), Loading @@ -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. */ Loading
aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsDocs.kt 0 → 100644 +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(), ) } } }
aws/sdk-codegen/src/main/kotlin/software/amazon/smithy/rustsdk/AwsFluentClientDecorator.kt +6 −51 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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) { Loading Loading @@ -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) { Loading @@ -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 Loading
aws/sdk-codegen/src/test/kotlin/AwsReadmeDecoratorTest.kt→aws/sdk-codegen/src/test/kotlin/AwsCrateDocsDecoratorTest.kt +9 −5 Original line number Diff line number Diff line Loading @@ -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( Loading @@ -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> Loading @@ -44,7 +48,7 @@ class AwsReadmeDecoratorTest { More text. """.trimIndent(), AwsSdkReadmeGenerator().normalizeDescription( AwsCrateDocGenerator(codegenContext).normalizeDescription( "", """ <p>Some text introducing a list: Loading Loading @@ -81,7 +85,7 @@ class AwsReadmeDecoratorTest { Some trailing text. """.trimIndent(), AwsSdkReadmeGenerator().normalizeDescription( AwsCrateDocGenerator(codegenContext).normalizeDescription( "", """ <p>Some text introducing a description list: Loading