Unverified Commit 4cc2f955 authored by Harry Barber's avatar Harry Barber Committed by GitHub
Browse files

Improve code generated documentation (#2019)

parent 0f26eade
Loading
Loading
Loading
Loading
+64 −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.rust.codegen.server.smithy.generators

import software.amazon.smithy.model.shapes.OperationShape
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.rust
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.CodegenTarget
import software.amazon.smithy.rust.codegen.core.smithy.Errors
import software.amazon.smithy.rust.codegen.core.smithy.Inputs
import software.amazon.smithy.rust.codegen.core.smithy.Outputs
import software.amazon.smithy.rust.codegen.core.smithy.generators.error.errorSymbol
import software.amazon.smithy.rust.codegen.core.util.inputShape
import software.amazon.smithy.rust.codegen.core.util.outputShape
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase

/**
Generates a stub for use within documentation.
 */
class DocHandlerGenerator(private val operation: OperationShape, private val commentToken: String = "//", private val codegenContext: CodegenContext) {
    private val model = codegenContext.model
    private val symbolProvider = codegenContext.symbolProvider
    private val crateName = codegenContext.settings.moduleName.toSnakeCase()

    /**
     * Returns the function signature for an operation handler implementation. Used in the documentation.
     */
    private fun OperationShape.docSignature(): Writable {
        val inputSymbol = symbolProvider.toSymbol(inputShape(model))
        val outputSymbol = symbolProvider.toSymbol(outputShape(model))
        val errorSymbol = errorSymbol(model, symbolProvider, CodegenTarget.SERVER)

        val outputT = if (errors.isEmpty()) {
            outputSymbol.name
        } else {
            "Result<${outputSymbol.name}, ${errorSymbol.name}>"
        }

        return writable {
            if (!errors.isEmpty()) {
                rust("$commentToken ## use $crateName::${Errors.namespace}::${errorSymbol.name};")
            }
            rust(
                """
                $commentToken ## use $crateName::${Inputs.namespace}::${inputSymbol.name};
                $commentToken ## use $crateName::${Outputs.namespace}::${outputSymbol.name};
                $commentToken async fn handler(input: ${inputSymbol.name}) -> $outputT {
                $commentToken     todo!()
                $commentToken }
                """.trimIndent(),
            )
        }
    }

    fun render(writer: RustWriter) {
        operation.docSignature()(writer)
    }
}
+71 −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.rust.codegen.server.smithy.generators

import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
import software.amazon.smithy.rust.codegen.core.rustlang.rust
import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
import software.amazon.smithy.rust.codegen.core.util.toPascalCase
import software.amazon.smithy.rust.codegen.core.util.toSnakeCase
import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency

class ServerOperationShapeGenerator(
    private val operations: List<OperationShape>,
    private val codegenContext: CodegenContext,
) {

    fun render(writer: RustWriter) {
        if (operations.isEmpty()) {
            return
        }

        val firstOperation = codegenContext.symbolProvider.toSymbol(operations[0])
        val firstOperationName = firstOperation.name.toPascalCase()
        val crateName = codegenContext.settings.moduleName.toSnakeCase()

        writer.rustTemplate(
            """
            //! A collection of zero-sized types (ZSTs) representing each operation defined in the service closure.
            //!
            //! ## Constructing an [`Operation`](#{SmithyHttpServer}::operation::OperationShapeExt)
            //!
            //! To apply middleware to specific operations the [`Operation`](#{SmithyHttpServer}::operation::Operation)
            //! API must be used.
            //!
            //! Using the [`OperationShapeExt`](#{SmithyHttpServer}::operation::OperationShapeExt) trait
            //! implemented on each ZST we can construct an [`Operation`](#{SmithyHttpServer}::operation::Operation)
            //! with appropriate constraints given by Smithy.
            //!
            //! #### Example
            //!
            //! ```no_run
            //! use $crateName::operation_shape::$firstOperationName;
            //! use #{SmithyHttpServer}::operation::OperationShapeExt;
            #{Handler:W}
            //!
            //! let operation = $firstOperationName::from_handler(handler)
            //!     .layer(todo!("Provide a layer implementation"));
            //! ```
            //!
            //! ## Use as Marker Structs
            //!
            //! The [plugin system](#{SmithyHttpServer}::plugin) also makes use of these ZSTs to parameterize
            //! [`Plugin`](#{SmithyHttpServer}::plugin::Plugin) implementations. The traits, such as
            //! [`OperationShape`](#{SmithyHttpServer}::operation::OperationShape) can be used to provide
            //! operation specific information to the [`Layer`](#{Tower}::Layer) being applied.
            """.trimIndent(),
            "SmithyHttpServer" to
                ServerCargoDependency.SmithyHttpServer(codegenContext.runtimeConfig).toType(),
            "Tower" to ServerCargoDependency.Tower.toType(),
            "Handler" to DocHandlerGenerator(operations[0], "//!", codegenContext)::render,
        )
        for (operation in operations) {
            ServerOperationGenerator(codegenContext, operation).render(writer)
        }
    }
}
+2 −4
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ open class ServerServiceGenerator(
) {
    private val index = TopDownIndex.of(codegenContext.model)
    protected val operations = index.getContainedOperations(codegenContext.serviceShape).sortedBy { it.id }
    private val serviceName = codegenContext.serviceShape.id.name.toString()

    /**
     * Render Service Specific code. Code will end up in different files via [useShapeWriter]. See `SymbolVisitor.kt`
@@ -42,7 +43,6 @@ open class ServerServiceGenerator(
     */
    fun render() {
        rustCrate.lib {
            val serviceName = codegenContext.serviceShape.id.name.toString()
            rust("##[doc(inline, hidden)]")
            rust("pub use crate::service::$serviceName;")
        }
@@ -85,9 +85,7 @@ open class ServerServiceGenerator(
                ),
            ),
        ) {
            for (operation in operations) {
                ServerOperationGenerator(codegenContext, operation).render(this)
            }
            ServerOperationShapeGenerator(operations, codegenContext).render(this)
        }

        // TODO(https://github.com/awslabs/smithy-rs/issues/1707): Remove, this is temporary.
+26 −2
Original line number Diff line number Diff line
@@ -23,7 +23,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocol

class ServerServiceGeneratorV2(
    codegenContext: CodegenContext,
    private val codegenContext: CodegenContext,
    private val protocol: ServerProtocol,
) {
    private val runtimeConfig = codegenContext.runtimeConfig
@@ -32,12 +32,14 @@ class ServerServiceGeneratorV2(
        arrayOf(
            "Bytes" to CargoDependency.Bytes.toType(),
            "Http" to CargoDependency.Http.toType(),
            "SmithyHttp" to CargoDependency.smithyHttp(runtimeConfig).toType(),
            "HttpBody" to CargoDependency.HttpBody.toType(),
            "SmithyHttpServer" to smithyHttpServer,
            "Tower" to CargoDependency.Tower.toType(),
        )
    private val model = codegenContext.model
    private val symbolProvider = codegenContext.symbolProvider
    val crateName = codegenContext.settings.moduleName.toSnakeCase()

    private val service = codegenContext.serviceShape
    private val serviceName = service.id.name.toPascalCase()
@@ -101,6 +103,22 @@ class ServerServiceGeneratorV2(
                ///
                /// This should be an async function satisfying the [`Handler`](#{SmithyHttpServer}::operation::Handler) trait.
                /// See the [operation module documentation](#{SmithyHttpServer}::operation) for more information.
                ///
                /// ## Example
                ///
                /// ```no_run
                /// use $crateName::$serviceName;
                ///
                #{Handler:W}
                ///
                /// let app = $serviceName::builder_without_plugins()
                ///     .$fieldName(handler)
                ///     /* Set other handlers */
                ///     .build()
                ///     .unwrap();
                /// ## let app: $serviceName<#{SmithyHttpServer}::routing::Route<#{SmithyHttp}::body::SdkBody>> = app;
                /// ```
                ///
                pub fn $fieldName<HandlerType, Extensions>(self, handler: HandlerType) -> Self
                where
                    HandlerType: #{SmithyHttpServer}::operation::Handler<crate::operation_shape::$structName, Extensions>,
@@ -138,6 +156,7 @@ class ServerServiceGeneratorV2(
                }
                """,
                "Protocol" to protocol.markerStruct(),
                "Handler" to DocHandlerGenerator(operationShape, "///", codegenContext)::render,
                *codegenScope,
            )

@@ -179,6 +198,9 @@ class ServerServiceGeneratorV2(
            /// Constructs a [`$serviceName`] from the arguments provided to the builder.
            ///
            /// Forgetting to register a handler for one or more operations will result in an error.
            ///
            /// Check out [`$builderName::build_unchecked`] if you'd prefer the service to return status code 500 when an
            /// unspecified route requested.
            pub fn build(self) -> Result<$serviceName<#{SmithyHttpServer}::routing::Route<$builderBodyGenericTypeName>>, MissingOperationsError>
            {
                let router = {
@@ -343,7 +365,7 @@ class ServerServiceGeneratorV2(

                /// Constructs a builder for [`$serviceName`].
                ///
                /// Use [`$serviceName::builder_without_plugins`] if you need to specify plugins.
                /// Use [`$serviceName::builder_with_plugins`] if you need to specify plugins.
                pub fn builder_without_plugins<Body>() -> $builderName<Body, #{SmithyHttpServer}::plugin::IdentityPlugin> {
                    Self::builder_with_plugins(#{SmithyHttpServer}::plugin::IdentityPlugin)
                }
@@ -415,6 +437,8 @@ class ServerServiceGeneratorV2(
    private fun missingOperationsError(): Writable = writable {
        rust(
            """
            /// The error encountered when calling the [`$builderName::build`] method if one or more operation handlers are not
            /// specified.
            ##[derive(Debug)]
            pub struct MissingOperationsError {
                operation_names2setter_methods: std::collections::HashMap<&'static str, &'static str>,