Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve code generated documentation #2019

Merged
merged 8 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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