Unverified Commit 0da92a43 authored by John DiSanti's avatar John DiSanti Committed by GitHub
Browse files

Move fluent builders and hide the parse strict response impl struct (#2401)

* Hide the operation parse impl structs

* Move fluent builders into per-operation modules
parent 3d408352
Loading
Loading
Loading
Loading
+175 −151
Original line number Diff line number Diff line
@@ -23,12 +23,10 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
import software.amazon.smithy.rust.codegen.core.rustlang.RustType
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
import software.amazon.smithy.rust.codegen.core.rustlang.asArgumentType
import software.amazon.smithy.rust.codegen.core.rustlang.asOptional
import software.amazon.smithy.rust.codegen.core.rustlang.deprecatedShape
import software.amazon.smithy.rust.codegen.core.rustlang.docLink
import software.amazon.smithy.rust.codegen.core.rustlang.docs
import software.amazon.smithy.rust.codegen.core.rustlang.documentShape
import software.amazon.smithy.rust.codegen.core.rustlang.escape
import software.amazon.smithy.rust.codegen.core.rustlang.normalizeHtml
@@ -82,6 +80,12 @@ class FluentClientGenerator(
            renderFluentClient(this)
        }

        operations.forEach { operation ->
            crate.withModule(operation.fluentBuilderModule(codegenContext, symbolProvider)) {
                renderFluentBuilder(operation)
            }
        }

        CustomizableOperationGenerator(codegenContext, generics).render(crate)
    }

@@ -159,7 +163,7 @@ class FluentClientGenerator(
        ) {
            operations.forEach { operation ->
                val name = symbolProvider.toSymbol(operation).name
                val fullPath = operation.fullyQualifiedFluentBuilder(symbolProvider)
                val fullPath = operation.fullyQualifiedFluentBuilder(codegenContext, symbolProvider)
                val maybePaginated = if (operation.isPaginated(model)) {
                    "\n/// This operation supports pagination; See [`into_paginator()`]($fullPath::into_paginator)."
                } else {
@@ -170,10 +174,13 @@ class FluentClientGenerator(
                val operationOk = symbolProvider.toSymbol(output)
                val operationErr = symbolProvider.symbolForOperationError(operation)

                val inputFieldsBody =
                    generateOperationShapeDocs(writer, symbolProvider, operation, model).joinToString("\n") {
                        "///   - $it"
                    }
                val inputFieldsBody = generateOperationShapeDocs(
                    writer,
                    codegenContext,
                    symbolProvider,
                    operation,
                    model,
                ).joinToString("\n") { "///   - $it" }

                val inputFieldsHead = if (inputFieldsBody.isNotEmpty()) {
                    "The fluent builder is configurable:"
@@ -206,32 +213,20 @@ class FluentClientGenerator(
                // Write a deprecation notice if this operation is deprecated.
                writer.deprecatedShape(operation)

                writer.rust(
                writer.rustTemplate(
                    """
                    pub fn ${
                        clientOperationFnName(
                            operation,
                            symbolProvider,
                        )
                    }(&self) -> fluent_builders::$name${generics.inst} {
                        fluent_builders::$name::new(self.handle.clone())
                    pub fn #{fnName}(&self) -> #{FluentBuilder}${generics.inst} {
                        #{FluentBuilder}::new(self.handle.clone())
                    }
                    """,
                    "fnName" to writable { rust(clientOperationFnName(operation, symbolProvider)) },
                    "FluentBuilder" to operation.fluentBuilderType(codegenContext, symbolProvider),
                )
            }
        }
        writer.withInlineModule(RustModule.new("fluent_builders", visibility = Visibility.PUBLIC, inline = true)) {
            docs(
                """
                Utilities to ergonomically construct a request to the service.
    }

                Fluent builders are created through the [`Client`](crate::client::Client) by calling
                one if its operation methods. After parameters are set using the builder methods,
                the `send` method can be called to initiate the request.
                """.trim(),
                newlinePrefix = "//! ",
            )
            operations.forEach { operation ->
    private fun RustWriter.renderFluentBuilder(operation: OperationShape) {
        val operationSymbol = symbolProvider.toSymbol(operation)
        val input = operation.inputShape(model)
        val baseDerives = symbolProvider.toSymbol(input).expectRustMetadata().derives
@@ -244,13 +239,14 @@ class FluentClientGenerator(
            """,
        )

        val builderName = operation.fluentBuilderType(codegenContext, symbolProvider).name
        documentShape(operation, model, autoSuppressMissingDocs = false)
        deprecatedShape(operation)
        Attribute(derive(derives.toSet())).render(this)
        rustTemplate(
            """
                    pub struct ${operationSymbol.name}#{generics:W} {
                        handle: std::sync::Arc<super::Handle${generics.inst}>,
            pub struct $builderName#{generics:W} {
                handle: std::sync::Arc<crate::client::Handle${generics.inst}>,
                inner: #{Inner}
            }
            """,
@@ -261,7 +257,7 @@ class FluentClientGenerator(
        )

        rustBlockTemplate(
                    "impl${generics.inst} ${operationSymbol.name}${generics.inst} #{bounds:W}",
            "impl${generics.inst} $builderName${generics.inst} #{bounds:W}",
            "client" to RuntimeType.smithyClient(runtimeConfig),
            "bounds" to generics.bounds,
        ) {
@@ -272,7 +268,7 @@ class FluentClientGenerator(
            rustTemplate(
                """
                /// Creates a new `${operationSymbol.name}`.
                        pub(crate) fn new(handle: std::sync::Arc<super::Handle${generics.inst}>) -> Self {
                pub(crate) fn new(handle: std::sync::Arc<crate::client::Handle${generics.inst}>) -> Self {
                    Self { handle, inner: Default::default() }
                }

@@ -359,8 +355,6 @@ class FluentClientGenerator(
        }
    }
}
    }
}

/**
 * For a given `operation` shape, return a list of strings where each string describes the name and input type of one of
@@ -370,12 +364,13 @@ class FluentClientGenerator(
 */
private fun generateOperationShapeDocs(
    writer: RustWriter,
    symbolProvider: SymbolProvider,
    codegenContext: ClientCodegenContext,
    symbolProvider: RustSymbolProvider,
    operation: OperationShape,
    model: Model,
): List<String> {
    val input = operation.inputShape(model)
    val fluentBuilderFullyQualifiedName = operation.fullyQualifiedFluentBuilder(symbolProvider)
    val fluentBuilderFullyQualifiedName = operation.fullyQualifiedFluentBuilder(codegenContext, symbolProvider)
    return input.members().map { memberShape ->
        val builderInputDoc = memberShape.asFluentBuilderInputDoc(symbolProvider)
        val builderInputLink = docLink("$fluentBuilderFullyQualifiedName::${symbolProvider.toMemberName(memberShape)}")
@@ -418,17 +413,46 @@ private fun generateShapeMemberDocs(
    }
}

private fun OperationShape.fluentBuilderModule(
    codegenContext: ClientCodegenContext,
    symbolProvider: RustSymbolProvider,
) = when (codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme) {
    true -> symbolProvider.moduleForBuilder(this)
    else -> RustModule.public(
        "fluent_builders",
        parent = ClientRustModule.client,
        documentation = """
            Utilities to ergonomically construct a request to the service.

            Fluent builders are created through the [`Client`](crate::client::Client) by calling
            one if its operation methods. After parameters are set using the builder methods,
            the `send` method can be called to initiate the request.
        """.trimIndent(),
    )
}

internal fun OperationShape.fluentBuilderType(
    codegenContext: ClientCodegenContext,
    symbolProvider: RustSymbolProvider,
): RuntimeType = fluentBuilderModule(codegenContext, symbolProvider).toType()
    .resolve(
        symbolProvider.toSymbol(this).name +
            when (codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme) {
                true -> "FluentBuilder"
                else -> ""
            },
    )

/**
 * Generate a valid fully-qualified Type for a fluent builder e.g.
 * `OperationShape(AssumeRole)` -> `"crate::client::fluent_builders::AssumeRole"`
 * `OperationShape(AssumeRole)` -> `"crate::operations::assume_role::AssumeRoleFluentBuilder"`
 *
 *  * _NOTE: This function generates the links that appear under **"The fluent builder is configurable:"**_
 */
private fun OperationShape.fullyQualifiedFluentBuilder(symbolProvider: SymbolProvider): String {
    val operationName = symbolProvider.toSymbol(this).name

    return "crate::client::fluent_builders::$operationName"
}
private fun OperationShape.fullyQualifiedFluentBuilder(
    codegenContext: ClientCodegenContext,
    symbolProvider: RustSymbolProvider,
): String = fluentBuilderType(codegenContext, symbolProvider).fullyQualifiedName()

/**
 * Generate a string that looks like a Rust function pointer for documenting a fluent builder method e.g.
+50 −6
Original line number Diff line number Diff line
@@ -6,15 +6,17 @@
package software.amazon.smithy.rust.codegen.client.smithy.generators.protocol

import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.client.fluentBuilderType
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.core.rustlang.docLink
import software.amazon.smithy.rust.codegen.core.rustlang.implBlock
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationCustomization
import software.amazon.smithy.rust.codegen.core.smithy.customize.OperationSection
@@ -26,7 +28,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.protocols.Protocol
import software.amazon.smithy.rust.codegen.core.util.inputShape

open class ClientProtocolGenerator(
    codegenContext: CodegenContext,
    private val codegenContext: ClientCodegenContext,
    private val protocol: Protocol,
    /**
     * Operations generate a `make_operation(&config)` method to build a `aws_smithy_http::Operation` that can be dispatched
@@ -52,7 +54,6 @@ open class ClientProtocolGenerator(
        val inputShape = operationShape.inputShape(model)

        // impl OperationInputShape { ... }
        val operationName = symbolProvider.toSymbol(operationShape).name
        inputWriter.implBlock(symbolProvider.toSymbol(inputShape)) {
            writeCustomizations(
                customizations,
@@ -61,17 +62,60 @@ open class ClientProtocolGenerator(
            makeOperationGenerator.generateMakeOperation(this, operationShape, customizations)
        }

        when (codegenContext.settings.codegenConfig.enableNewCrateOrganizationScheme) {
            true -> renderOperationStruct(operationWriter, operationShape, customizations)
            else -> oldRenderOperationStruct(operationWriter, operationShape, inputShape, customizations)
        }
    }

    private fun renderOperationStruct(
        operationWriter: RustWriter,
        operationShape: OperationShape,
        customizations: List<OperationCustomization>,
    ) {
        val operationName = symbolProvider.toSymbol(operationShape).name

        // pub struct Operation { ... }
        val fluentBuilderName = FluentClientGenerator.clientOperationFnName(operationShape, symbolProvider)
        operationWriter.rust(
            """
            /// `ParseStrictResponse` impl for `$operationName`.
            """,
        )
        Attribute(derive(RuntimeType.Clone, RuntimeType.Default, RuntimeType.Debug)).render(operationWriter)
        Attribute.NonExhaustive.render(operationWriter)
        Attribute.DocHidden.render(operationWriter)
        operationWriter.rust("pub struct $operationName;")
        operationWriter.implBlock(symbolProvider.toSymbol(operationShape)) {
            rustBlock("pub(crate) fn new() -> Self") {
                rust("Self")
            }

            writeCustomizations(customizations, OperationSection.OperationImplBlock(customizations))
        }
        traitGenerator.generateTraitImpls(operationWriter, operationShape, customizations)
    }

    // TODO(CrateReorganization): Remove this function when removing `enableNewCrateOrganizationScheme`
    private fun oldRenderOperationStruct(
        operationWriter: RustWriter,
        operationShape: OperationShape,
        inputShape: StructureShape,
        customizations: List<OperationCustomization>,
    ) {
        val operationName = symbolProvider.toSymbol(operationShape).name

        // pub struct Operation { ... }
        val fluentBuilderName = FluentClientGenerator.clientOperationFnName(operationShape, symbolProvider)
        operationWriter.rustTemplate(
            """
            /// Operation shape for `$operationName`.
            ///
            /// This is usually constructed for you using the the fluent builder returned by
            /// [`$fluentBuilderName`](${docLink("crate::client::Client::$fluentBuilderName")}).
            /// [`$fluentBuilderName`](#{fluentBuilder}).
            ///
            /// `ParseStrictResponse` impl for `$operationName`.
            """,
            "fluentBuilder" to operationShape.fluentBuilderType(codegenContext, symbolProvider),
        )
        Attribute(derive(RuntimeType.Clone, RuntimeType.Default, RuntimeType.Debug)).render(operationWriter)
        operationWriter.rustBlock("pub struct $operationName") {
+2 −1
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@ import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.shapes.StructureShape
import software.amazon.smithy.model.traits.ErrorTrait
import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
import software.amazon.smithy.rust.codegen.client.smithy.generators.http.ResponseBindingGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.ClientProtocolGenerator
import software.amazon.smithy.rust.codegen.client.smithy.generators.protocol.MakeOperationGenerator
@@ -47,7 +48,7 @@ import software.amazon.smithy.rust.codegen.core.util.outputShape
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase

class HttpBoundProtocolGenerator(
    codegenContext: CodegenContext,
    codegenContext: ClientCodegenContext,
    protocol: Protocol,
) : ClientProtocolGenerator(
    codegenContext,
+1 −1
Original line number Diff line number Diff line
@@ -92,7 +92,7 @@ private class TestProtocolMakeOperationGenerator(

// A stubbed test protocol to do enable testing intentionally broken protocols
private class TestProtocolGenerator(
    codegenContext: CodegenContext,
    codegenContext: ClientCodegenContext,
    protocol: Protocol,
    httpRequestBuilder: String,
    body: String,