Skip to content

Commit f0929e7

Browse files
authored
Allow server decorators to inject methods on config (#3111)
PR #3095 added a code-generated `${serviceName}Config` object on which users can register layers and plugins. For example: ```rust let config = PokemonServiceConfig::builder() .layer(layers) .http_plugin(authn_plugin) .model_plugin(authz_plugin) .build(); ``` This PR makes it so that server decorators can inject methods on this config builder object. These methods can apply arbitrary layers, HTTP plugins, and/or model plugins. Moreover, the decorator can configure whether invoking such method is required or not. For example, a decorator can inject an `aws_auth` method that configures some plugins using its input arguments. Missing invocation of this method will result in the config failing to build: ```rust let _: SimpleServiceConfig< // No layers have been applied. tower::layer::util::Identity, // One HTTP plugin has been applied. PluginStack<IdentityPlugin, IdentityPlugin>, // One model plugin has been applied. PluginStack<IdentityPlugin, IdentityPlugin>, > = SimpleServiceConfig::builder() // This method has been injected in the config builder! .aws_auth("a".repeat(69).to_owned(), 69) // The method will apply one HTTP plugin and one model plugin, // configuring them with the input arguments. Configuration can be // declared to be fallible, in which case we get a `Result` we unwrap // here. .expect("failed to configure aws_auth") .build() // Since `aws_auth` has been marked as required, if the user misses // invoking it, this would panic here. .unwrap(); ``` ---- _By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice._
1 parent cc303ab commit f0929e7

File tree

9 files changed

+614
-33
lines changed

9 files changed

+614
-33
lines changed

codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ class TestWriterDelegator(
294294
}
295295

296296
/**
297-
* Generate a newtest module
297+
* Generate a new test module
298298
*
299299
* This should only be used in test code—the generated module name will be something like `tests_123`
300300
*/

codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCargoDependency.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ object ServerCargoDependency {
2222
val Nom: CargoDependency = CargoDependency("nom", CratesIo("7"))
2323
val OnceCell: CargoDependency = CargoDependency("once_cell", CratesIo("1.13"))
2424
val PinProjectLite: CargoDependency = CargoDependency("pin-project-lite", CratesIo("0.2"))
25+
val ThisError: CargoDependency = CargoDependency("thiserror", CratesIo("1.0"))
2526
val Tower: CargoDependency = CargoDependency("tower", CratesIo("0.4"))
2627
val TokioDev: CargoDependency = CargoDependency("tokio", CratesIo("1.23.1"), scope = DependencyScope.Dev)
2728
val Regex: CargoDependency = CargoDependency("regex", CratesIo("1.5.5"))

codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.generators.Unconstraine
7878
import software.amazon.smithy.rust.codegen.server.smithy.generators.UnconstrainedMapGenerator
7979
import software.amazon.smithy.rust.codegen.server.smithy.generators.UnconstrainedUnionGenerator
8080
import software.amazon.smithy.rust.codegen.server.smithy.generators.ValidationExceptionConversionGenerator
81+
import software.amazon.smithy.rust.codegen.server.smithy.generators.isBuilderFallible
8182
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocol
8283
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocolGenerator
8384
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocolTestGenerator
@@ -591,11 +592,15 @@ open class ServerCodegenVisitor(
591592
logger.info("[rust-server-codegen] Generating a service $shape")
592593
val serverProtocol = protocolGeneratorFactory.protocol(codegenContext) as ServerProtocol
593594

595+
val configMethods = codegenDecorator.configMethods(codegenContext)
596+
val isConfigBuilderFallible = configMethods.isBuilderFallible()
597+
594598
// Generate root.
595599
rustCrate.lib {
596600
ServerRootGenerator(
597601
serverProtocol,
598602
codegenContext,
603+
isConfigBuilderFallible,
599604
).render(this)
600605
}
601606

@@ -612,9 +617,10 @@ open class ServerCodegenVisitor(
612617
ServerServiceGenerator(
613618
codegenContext,
614619
serverProtocol,
620+
isConfigBuilderFallible,
615621
).render(this)
616622

617-
ServiceConfigGenerator(codegenContext).render(this)
623+
ServiceConfigGenerator(codegenContext, configMethods).render(this)
618624

619625
ScopeMacroGenerator(codegenContext).render(this)
620626
}

codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/customize/ServerCodegenDecorator.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import software.amazon.smithy.rust.codegen.core.smithy.protocols.ProtocolMap
1515
import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext
1616
import software.amazon.smithy.rust.codegen.server.smithy.ServerRustSettings
1717
import software.amazon.smithy.rust.codegen.server.smithy.ValidationResult
18+
import software.amazon.smithy.rust.codegen.server.smithy.generators.ConfigMethod
1819
import software.amazon.smithy.rust.codegen.server.smithy.generators.ValidationExceptionConversionGenerator
1920
import software.amazon.smithy.rust.codegen.server.smithy.generators.protocol.ServerProtocolGenerator
2021
import java.util.logging.Logger
@@ -41,6 +42,12 @@ interface ServerCodegenDecorator : CoreCodegenDecorator<ServerCodegenContext, Se
4142
* Therefore, ensure that all the structure shapes returned by this method are not in the service's closure.
4243
*/
4344
fun postprocessGenerateAdditionalStructures(operationShape: OperationShape): List<StructureShape> = emptyList()
45+
46+
/**
47+
* Configuration methods that should be injected into the `${serviceName}Config` struct to allow users to configure
48+
* pre-applied layers and plugins.
49+
*/
50+
fun configMethods(codegenContext: ServerCodegenContext): List<ConfigMethod> = emptyList()
4451
}
4552

4653
/**
@@ -74,10 +81,11 @@ class CombinedServerCodegenDecorator(decorators: List<ServerCodegenDecorator>) :
7481
decorator.postprocessValidationExceptionNotAttachedErrorMessage(accumulated)
7582
}
7683

77-
override fun postprocessGenerateAdditionalStructures(operationShape: OperationShape): List<StructureShape> {
78-
return orderedDecorators.map { decorator -> decorator.postprocessGenerateAdditionalStructures(operationShape) }
79-
.flatten()
80-
}
84+
override fun postprocessGenerateAdditionalStructures(operationShape: OperationShape): List<StructureShape> =
85+
orderedDecorators.flatMap { it.postprocessGenerateAdditionalStructures(operationShape) }
86+
87+
override fun configMethods(codegenContext: ServerCodegenContext): List<ConfigMethod> =
88+
orderedDecorators.flatMap { it.configMethods(codegenContext) }
8189

8290
companion object {
8391
fun fromClasspath(

codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerRootGenerator.kt

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.ServerRustModule.Output
3131
open class ServerRootGenerator(
3232
val protocol: ServerProtocol,
3333
private val codegenContext: ServerCodegenContext,
34+
private val isConfigBuilderFallible: Boolean,
3435
) {
3536
private val index = TopDownIndex.of(codegenContext.model)
3637
private val operations = index.getContainedOperations(codegenContext.serviceShape).toSortedSet(
@@ -57,6 +58,8 @@ open class ServerRootGenerator(
5758
}
5859
.join("//!\n")
5960

61+
val unwrapConfigBuilder = if (isConfigBuilderFallible) ".expect(\"config failed to build\")" else ""
62+
6063
writer.rustTemplate(
6164
"""
6265
//! A fast and customizable Rust implementation of the $serviceName Smithy service.
@@ -75,7 +78,10 @@ open class ServerRootGenerator(
7578
//! ## async fn dummy() {
7679
//! use $crateName::{$serviceName, ${serviceName}Config};
7780
//!
78-
//! ## let app = $serviceName::builder(${serviceName}Config::builder().build()).build_unchecked();
81+
//! ## let app = $serviceName::builder(
82+
//! ## ${serviceName}Config::builder()
83+
//! ## .build()$unwrapConfigBuilder
84+
//! ## ).build_unchecked();
7985
//! let server = app.into_make_service();
8086
//! let bind: SocketAddr = "127.0.0.1:6969".parse()
8187
//! .expect("unable to parse the server bind address and port");
@@ -92,7 +98,10 @@ open class ServerRootGenerator(
9298
//! use $crateName::$serviceName;
9399
//!
94100
//! ## async fn dummy() {
95-
//! ## let app = $serviceName::builder(${serviceName}Config::builder().build()).build_unchecked();
101+
//! ## let app = $serviceName::builder(
102+
//! ## ${serviceName}Config::builder()
103+
//! ## .build()$unwrapConfigBuilder
104+
//! ## ).build_unchecked();
96105
//! let handler = LambdaHandler::new(app);
97106
//! lambda_http::run(handler).await.unwrap();
98107
//! ## }
@@ -118,7 +127,7 @@ open class ServerRootGenerator(
118127
//! let http_plugins = HttpPlugins::new()
119128
//! .push(LoggingPlugin)
120129
//! .push(MetricsPlugin);
121-
//! let config = ${serviceName}Config::builder().build();
130+
//! let config = ${serviceName}Config::builder().build()$unwrapConfigBuilder;
122131
//! let builder: $builderName<Body, _, _, _> = $serviceName::builder(config);
123132
//! ```
124133
//!
@@ -183,13 +192,13 @@ open class ServerRootGenerator(
183192
//!
184193
//! ## Example
185194
//!
186-
//! ```rust
195+
//! ```rust,no_run
187196
//! ## use std::net::SocketAddr;
188197
//! use $crateName::{$serviceName, ${serviceName}Config};
189198
//!
190199
//! ##[#{Tokio}::main]
191200
//! pub async fn main() {
192-
//! let config = ${serviceName}Config::builder().build();
201+
//! let config = ${serviceName}Config::builder().build()$unwrapConfigBuilder;
193202
//! let app = $serviceName::builder(config)
194203
${builderFieldNames.values.joinToString("\n") { "//! .$it($it)" }}
195204
//! .build()
@@ -236,6 +245,23 @@ open class ServerRootGenerator(
236245
fun render(rustWriter: RustWriter) {
237246
documentation(rustWriter)
238247

239-
rustWriter.rust("pub use crate::service::{$serviceName, ${serviceName}Config, ${serviceName}ConfigBuilder, ${serviceName}Builder, MissingOperationsError};")
248+
// Only export config builder error if fallible.
249+
val configErrorReExport = if (isConfigBuilderFallible) {
250+
"${serviceName}ConfigError,"
251+
} else {
252+
""
253+
}
254+
rustWriter.rust(
255+
"""
256+
pub use crate::service::{
257+
$serviceName,
258+
${serviceName}Config,
259+
${serviceName}ConfigBuilder,
260+
$configErrorReExport
261+
${serviceName}Builder,
262+
MissingOperationsError
263+
};
264+
""",
265+
)
240266
}
241267
}

codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.ServerRustModule.Output
3333
class ServerServiceGenerator(
3434
private val codegenContext: ServerCodegenContext,
3535
private val protocol: ServerProtocol,
36+
private val isConfigBuilderFallible: Boolean,
3637
) {
3738
private val runtimeConfig = codegenContext.runtimeConfig
3839
private val smithyHttpServer = ServerCargoDependency.smithyHttpServer(runtimeConfig).toType()
@@ -107,6 +108,11 @@ class ServerServiceGenerator(
107108
val docHandler = DocHandlerGenerator(codegenContext, operationShape, "handler", "///")
108109
val handler = docHandler.docSignature()
109110
val handlerFixed = docHandler.docFixedSignature()
111+
val unwrapConfigBuilder = if (isConfigBuilderFallible) {
112+
".expect(\"config failed to build\")"
113+
} else {
114+
""
115+
}
110116
rustTemplate(
111117
"""
112118
/// Sets the [`$structName`](crate::operation_shape::$structName) operation.
@@ -123,7 +129,7 @@ class ServerServiceGenerator(
123129
///
124130
#{Handler:W}
125131
///
126-
/// let config = ${serviceName}Config::builder().build();
132+
/// let config = ${serviceName}Config::builder().build()$unwrapConfigBuilder;
127133
/// let app = $serviceName::builder(config)
128134
/// .$fieldName(handler)
129135
/// /* Set other handlers */
@@ -186,7 +192,7 @@ class ServerServiceGenerator(
186192
///
187193
#{HandlerFixed:W}
188194
///
189-
/// let config = ${serviceName}Config::builder().build();
195+
/// let config = ${serviceName}Config::builder().build()$unwrapConfigBuilder;
190196
/// let svc = #{Tower}::util::service_fn(handler);
191197
/// let app = $serviceName::builder(config)
192198
/// .${fieldName}_service(svc)

0 commit comments

Comments
 (0)