From 0f26eade6047b6407fc866bc53c655fbec00a127 Mon Sep 17 00:00:00 2001 From: Lindsay Roberts Date: Tue, 22 Nov 2022 15:25:10 +0200 Subject: [PATCH 1/2] Add const to generated enum values() (#2011) * Add const to generated enum values() Enum values() functions return static arrays of static strings and could be of compile time use. Make callable in const contexts. * Add reference to PR in changelog next for const enum values() * Correct changelog next target to all for const enum values() Co-authored-by: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> --- CHANGELOG.next.toml | 6 ++++++ .../rust/codegen/core/smithy/generators/EnumGenerator.kt | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index fea153453a..f0a3bf3c70 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -478,3 +478,9 @@ x-amzn-errortype: com.example.service#InvalidRequestException references = ["smithy-rs#1982"] meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "server" } author = "david-perez" + +[[smithy-rs]] +message = "Make generated enum `values()` functions callable in const contexts." +references = ["smithy-rs#2011"] +meta = { "breaking" = false, "tada" = false, "bug" = false, "target" = "all" } +author = "lsr0" diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt index 0acfe2641d..95a46649ea 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt @@ -141,7 +141,7 @@ open class EnumGenerator( } docs("Returns all the `&str` representations of the enum members.") - rustBlock("pub fn $Values() -> &'static [&'static str]") { + rustBlock("pub const fn $Values() -> &'static [&'static str]") { withBlock("&[", "]") { val memberList = sortedMembers.joinToString(", ") { it.value.dq() } rust(memberList) @@ -198,7 +198,7 @@ open class EnumGenerator( } rust("/// Returns all the `&str` values of the enum members.") - rustBlock("pub fn $Values() -> &'static [&'static str]") { + rustBlock("pub const fn $Values() -> &'static [&'static str]") { withBlock("&[", "]") { val memberList = sortedMembers.joinToString(", ") { it.value.doubleQuote() } write(memberList) From 4cc2f9555e4f91640500510210cb5facec761067 Mon Sep 17 00:00:00 2001 From: Harry Barber <106155934+hlbarber@users.noreply.github.com> Date: Tue, 22 Nov 2022 16:46:25 +0000 Subject: [PATCH 2/2] Improve code generated documentation (#2019) Co-authored-by: Luca Palmieri <20745048+LukeMathWalker@users.noreply.github.com> --- .../smithy/generators/DocHandlerGenerator.kt | 64 +++++++++++++++++ .../ServerOperationShapeGenerator.kt | 71 +++++++++++++++++++ .../generators/ServerServiceGenerator.kt | 6 +- .../generators/ServerServiceGeneratorV2.kt | 28 +++++++- 4 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/DocHandlerGenerator.kt create mode 100644 codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationShapeGenerator.kt diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/DocHandlerGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/DocHandlerGenerator.kt new file mode 100644 index 0000000000..6407c24f1b --- /dev/null +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/DocHandlerGenerator.kt @@ -0,0 +1,64 @@ +/* + * 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) + } +} diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationShapeGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationShapeGenerator.kt new file mode 100644 index 0000000000..8ae62beca1 --- /dev/null +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationShapeGenerator.kt @@ -0,0 +1,71 @@ +/* + * 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, + 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) + } + } +} diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt index ca6bd0fe5a..b5b471b496 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt @@ -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. diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt index 512ccd6912..ca782f4da6 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt @@ -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(self, handler: HandlerType) -> Self where HandlerType: #{SmithyHttpServer}::operation::Handler, @@ -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() -> $builderName { 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>,