Skip to content

Commit

Permalink
Improve code generated documentation (#2019)
Browse files Browse the repository at this point in the history
Co-authored-by: Luca Palmieri <[email protected]>
  • Loading branch information
hlbarber and LukeMathWalker authored Nov 22, 2022
1 parent 0f26ead commit 4cc2f95
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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<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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ 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`
* which assigns a symbol location to each shape.
*/
fun render() {
rustCrate.lib {
val serviceName = codegenContext.serviceShape.id.name.toString()
rust("##[doc(inline, hidden)]")
rust("pub use crate::service::$serviceName;")
}
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -138,6 +156,7 @@ class ServerServiceGeneratorV2(
}
""",
"Protocol" to protocol.markerStruct(),
"Handler" to DocHandlerGenerator(operationShape, "///", codegenContext)::render,
*codegenScope,
)

Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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>,
Expand Down

0 comments on commit 4cc2f95

Please sign in to comment.