diff --git a/CHANGELOG.next.toml b/CHANGELOG.next.toml index fbf7652d2d..267d530fd5 100644 --- a/CHANGELOG.next.toml +++ b/CHANGELOG.next.toml @@ -506,3 +506,9 @@ The [`connection`](https://docs.rs/aws-smithy-http/latest/aws_smithy_http/connec references = ["smithy-rs#3092", "smithy-rs#3093"] meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "client" } author = "ysaito1001" + +[[smithy-rs]] +message = "Service builder initialization now takes in a `${serviceName}Config` object on which plugins and layers should be registered. The `builder_with_plugins` and `builder_without_plugins` methods on the service builder, as well as the `layer` method on the built service have been deprecated, and will be removed in a future release. See the [upgrade guidance](https://github.com/awslabs/smithy-rs/discussions/3096) for more details." +references = ["smithy-rs#3095", "smithy-rs#3096"] +meta = { "breaking" = true, "tada" = false, "bug" = true, "target" = "server" } +author = "david-perez" diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt index 764f3b08c3..8965e3eb9d 100644 --- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt +++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt @@ -551,10 +551,7 @@ class RustWriter private constructor( if (this.className.contains("AbstractCodeWriter") || this.className.startsWith("java.lang")) { return false } - if (this.fileName == "RustWriter.kt") { - return false - } - return true + return this.fileName != "RustWriter.kt" } private val preamble = mutableListOf() diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt index c27cba8ae5..6f66c72166 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/ServerCodegenVisitor.kt @@ -73,6 +73,7 @@ import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerRootGe import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerRuntimeTypesReExportsGenerator import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerServiceGenerator import software.amazon.smithy.rust.codegen.server.smithy.generators.ServerStructureConstrainedTraitImpl +import software.amazon.smithy.rust.codegen.server.smithy.generators.ServiceConfigGenerator import software.amazon.smithy.rust.codegen.server.smithy.generators.UnconstrainedCollectionGenerator import software.amazon.smithy.rust.codegen.server.smithy.generators.UnconstrainedMapGenerator import software.amazon.smithy.rust.codegen.server.smithy.generators.UnconstrainedUnionGenerator @@ -590,7 +591,7 @@ open class ServerCodegenVisitor( logger.info("[rust-server-codegen] Generating a service $shape") val serverProtocol = protocolGeneratorFactory.protocol(codegenContext) as ServerProtocol - // Generate root + // Generate root. rustCrate.lib { ServerRootGenerator( serverProtocol, @@ -598,21 +599,23 @@ open class ServerCodegenVisitor( ).render(this) } - // Generate server re-exports + // Generate server re-exports. rustCrate.withModule(ServerRustModule.Server) { ServerRuntimeTypesReExportsGenerator(codegenContext).render(this) } - // Generate protocol tests + // Generate protocol tests. protocolTests() - // Generate service module + // Generate service module. rustCrate.withModule(ServerRustModule.Service) { ServerServiceGenerator( codegenContext, serverProtocol, ).render(this) + ServiceConfigGenerator(codegenContext).render(this) + ScopeMacroGenerator(codegenContext).render(this) } } diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerRootGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerRootGenerator.kt index fbf938265e..a1ea4b90f6 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerRootGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerRootGenerator.kt @@ -73,9 +73,9 @@ open class ServerRootGenerator( //! ```rust,no_run //! ## use std::net::SocketAddr; //! ## async fn dummy() { - //! use $crateName::$serviceName; + //! use $crateName::{$serviceName, ${serviceName}Config}; //! - //! ## let app = $serviceName::builder_without_plugins().build_unchecked(); + //! ## let app = $serviceName::builder(${serviceName}Config::builder().build()).build_unchecked(); //! let server = app.into_make_service(); //! let bind: SocketAddr = "127.0.0.1:6969".parse() //! .expect("unable to parse the server bind address and port"); @@ -92,7 +92,7 @@ open class ServerRootGenerator( //! use $crateName::$serviceName; //! //! ## async fn dummy() { - //! ## let app = $serviceName::builder_without_plugins().build_unchecked(); + //! ## let app = $serviceName::builder(${serviceName}Config::builder().build()).build_unchecked(); //! let handler = LambdaHandler::new(app); //! lambda_http::run(handler).await.unwrap(); //! ## } @@ -100,28 +100,26 @@ open class ServerRootGenerator( //! //! ## Building the $serviceName //! - //! To construct [`$serviceName`] we use [`$builderName`] returned by [`$serviceName::builder_without_plugins`] - //! or [`$serviceName::builder_with_plugins`]. + //! To construct [`$serviceName`] we use [`$builderName`] returned by [`$serviceName::builder`]. //! //! #### Plugins //! - //! The [`$serviceName::builder_with_plugins`] method, returning [`$builderName`], - //! accepts a plugin marked with [`HttpMarker`](aws_smithy_http_server::plugin::HttpMarker) and a - //! plugin marked with [`ModelMarker`](aws_smithy_http_server::plugin::ModelMarker). + //! The [`$serviceName::builder`] method, returning [`$builderName`], + //! accepts a config object on which plugins can be registered. //! Plugins allow you to build middleware which is aware of the operation it is being applied to. //! - //! ```rust - //! ## use #{SmithyHttpServer}::plugin::IdentityPlugin; + //! ```rust,no_run //! ## use #{SmithyHttpServer}::plugin::IdentityPlugin as LoggingPlugin; //! ## use #{SmithyHttpServer}::plugin::IdentityPlugin as MetricsPlugin; //! ## use #{Hyper}::Body; //! use #{SmithyHttpServer}::plugin::HttpPlugins; - //! use $crateName::{$serviceName, $builderName}; + //! use $crateName::{$serviceName, ${serviceName}Config, $builderName}; //! //! let http_plugins = HttpPlugins::new() //! .push(LoggingPlugin) //! .push(MetricsPlugin); - //! let builder: $builderName = $serviceName::builder_with_plugins(http_plugins, IdentityPlugin); + //! let config = ${serviceName}Config::builder().build(); + //! let builder: $builderName = $serviceName::builder(config); //! ``` //! //! Check out [`#{SmithyHttpServer}::plugin`] to learn more about plugins. @@ -136,7 +134,7 @@ open class ServerRootGenerator( //! * A `Result` if your operation has modeled errors, or //! * An `Output` otherwise. //! - //! ```rust + //! ```rust,no_run //! ## struct Input; //! ## struct Output; //! ## struct Error; @@ -147,7 +145,7 @@ open class ServerRootGenerator( //! //! Handlers can accept up to 8 extractors: //! - //! ```rust + //! ```rust,no_run //! ## struct Input; //! ## struct Output; //! ## struct Error; @@ -187,11 +185,12 @@ open class ServerRootGenerator( //! //! ```rust //! ## use std::net::SocketAddr; - //! use $crateName::$serviceName; + //! use $crateName::{$serviceName, ${serviceName}Config}; //! //! ##[#{Tokio}::main] //! pub async fn main() { - //! let app = $serviceName::builder_without_plugins() + //! let config = ${serviceName}Config::builder().build(); + //! let app = $serviceName::builder(config) ${builderFieldNames.values.joinToString("\n") { "//! .$it($it)" }} //! .build() //! .expect("failed to build an instance of $serviceName"); @@ -237,6 +236,6 @@ open class ServerRootGenerator( fun render(rustWriter: RustWriter) { documentation(rustWriter) - rustWriter.rust("pub use crate::service::{$serviceName, ${serviceName}Builder, MissingOperationsError};") + rustWriter.rust("pub use crate::service::{$serviceName, ${serviceName}Config, ${serviceName}ConfigBuilder, ${serviceName}Builder, MissingOperationsError};") } } 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 2b4e2ee2e9..97963cbb40 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 @@ -53,7 +53,6 @@ class ServerServiceGenerator( private val serviceId = service.id private val serviceName = serviceId.name.toPascalCase() private val builderName = "${serviceName}Builder" - private val builderBodyGenericTypeName = "Body" /** Calculate all `operationShape`s contained within the `ServiceShape`. */ private val index = TopDownIndex.of(codegenContext.model) @@ -118,18 +117,19 @@ class ServerServiceGenerator( /// ## Example /// /// ```no_run - /// use $crateName::$serviceName; + /// use $crateName::{$serviceName, ${serviceName}Config}; /// #{HandlerImports:W} /// #{Handler:W} /// - /// let app = $serviceName::builder_without_plugins() + /// let config = ${serviceName}Config::builder().build(); + /// let app = $serviceName::builder(config) /// .$fieldName(handler) /// /* Set other handlers */ /// .build() /// .unwrap(); - /// ## let app: $serviceName<#{SmithyHttpServer}::routing::Route<#{SmithyHttp}::body::SdkBody>> = app; + /// ## let app: $serviceName<#{SmithyHttpServer}::routing::RoutingService<#{Router}<#{SmithyHttpServer}::routing::Route>, #{Protocol}>> = app; /// ``` /// pub fn $fieldName(self, handler: HandlerType) -> Self @@ -137,22 +137,22 @@ class ServerServiceGenerator( HandlerType: #{SmithyHttpServer}::operation::Handler, ModelPl: #{SmithyHttpServer}::plugin::Plugin< - $serviceName, + $serviceName, crate::operation_shape::$structName, #{SmithyHttpServer}::operation::IntoService >, #{SmithyHttpServer}::operation::UpgradePlugin::: #{SmithyHttpServer}::plugin::Plugin< - $serviceName, + $serviceName, crate::operation_shape::$structName, ModelPl::Output >, HttpPl: #{SmithyHttpServer}::plugin::Plugin< - $serviceName, + $serviceName, crate::operation_shape::$structName, < #{SmithyHttpServer}::operation::UpgradePlugin:: as #{SmithyHttpServer}::plugin::Plugin< - $serviceName, + $serviceName, crate::operation_shape::$structName, ModelPl::Output > @@ -180,19 +180,20 @@ class ServerServiceGenerator( /// ## Example /// /// ```no_run - /// use $crateName::$serviceName; + /// use $crateName::{$serviceName, ${serviceName}Config}; /// #{HandlerImports:W} /// #{HandlerFixed:W} /// + /// let config = ${serviceName}Config::builder().build(); /// let svc = #{Tower}::util::service_fn(handler); - /// let app = $serviceName::builder_without_plugins() + /// let app = $serviceName::builder(config) /// .${fieldName}_service(svc) /// /* Set other handlers */ /// .build() /// .unwrap(); - /// ## let app: $serviceName<#{SmithyHttpServer}::routing::Route<#{SmithyHttp}::body::SdkBody>> = app; + /// ## let app: $serviceName<#{SmithyHttpServer}::routing::RoutingService<#{Router}<#{SmithyHttpServer}::routing::Route>, #{Protocol}>> = app; /// ``` /// pub fn ${fieldName}_service(self, service: S) -> Self @@ -200,22 +201,22 @@ class ServerServiceGenerator( S: #{SmithyHttpServer}::operation::OperationService, ModelPl: #{SmithyHttpServer}::plugin::Plugin< - $serviceName, + $serviceName, crate::operation_shape::$structName, #{SmithyHttpServer}::operation::Normalize >, #{SmithyHttpServer}::operation::UpgradePlugin::: #{SmithyHttpServer}::plugin::Plugin< - $serviceName, + $serviceName, crate::operation_shape::$structName, ModelPl::Output >, HttpPl: #{SmithyHttpServer}::plugin::Plugin< - $serviceName, + $serviceName, crate::operation_shape::$structName, < #{SmithyHttpServer}::operation::UpgradePlugin:: as #{SmithyHttpServer}::plugin::Plugin< - $serviceName, + $serviceName, crate::operation_shape::$structName, ModelPl::Output > @@ -246,6 +247,7 @@ class ServerServiceGenerator( self } """, + "Router" to protocol.routerType(), "Protocol" to protocol.markerStruct(), "Handler" to handler, "HandlerFixed" to handlerFixed, @@ -294,8 +296,18 @@ class ServerServiceGenerator( /// 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> + /// unspecified route is requested. + pub fn build(self) -> Result< + $serviceName< + #{SmithyHttpServer}::routing::RoutingService< + #{Router}, + #{Protocol}, + >, + >, + MissingOperationsError, + > + where + L: #{Tower}::Layer<#{SmithyHttpServer}::routing::Route>, { let router = { use #{SmithyHttpServer}::operation::OperationShape; @@ -312,15 +324,16 @@ class ServerServiceGenerator( #{Router}::from_iter([#{RoutesArrayElements:W}]) }; - Ok($serviceName { - router: #{SmithyHttpServer}::routing::RoutingService::new(router), - }) + let svc = #{SmithyHttpServer}::routing::RoutingService::new(router); + let svc = svc.map(|s| s.layer(self.layer)); + Ok($serviceName { svc }) } """, + *codegenScope, + "Protocol" to protocol.markerStruct(), "Router" to protocol.routerType(), "NullabilityChecks" to nullabilityChecks, "RoutesArrayElements" to routesArrayElements, - "SmithyHttpServer" to smithyHttpServer, "PatternInitializations" to patternInitializations(), ) } @@ -376,32 +389,38 @@ class ServerServiceGenerator( /// /// Check out [`$builderName::build`] if you'd prefer the builder to fail if one or more operations do /// not have a registered handler. - pub fn build_unchecked(self) -> $serviceName<#{SmithyHttpServer}::routing::Route<$builderBodyGenericTypeName>> + pub fn build_unchecked(self) -> $serviceName where - $builderBodyGenericTypeName: Send + 'static + Body: Send + 'static, + L: #{Tower}::Layer< + #{SmithyHttpServer}::routing::RoutingService<#{Router}<#{SmithyHttpServer}::routing::Route>, #{Protocol}> + > { let router = #{Router}::from_iter([#{Pairs:W}]); - $serviceName { - router: #{SmithyHttpServer}::routing::RoutingService::new(router), - } + let svc = self + .layer + .layer(#{SmithyHttpServer}::routing::RoutingService::new(router)); + $serviceName { svc } } """, + *codegenScope, + "Protocol" to protocol.markerStruct(), "Router" to protocol.routerType(), "Pairs" to pairs, - "SmithyHttpServer" to smithyHttpServer, ) } /** Returns a `Writable` containing the builder struct definition and its implementations. */ private fun builder(): Writable = writable { - val builderGenerics = listOf(builderBodyGenericTypeName, "HttpPl", "ModelPl").joinToString(", ") + val builderGenerics = listOf("Body", "L", "HttpPl", "ModelPl").joinToString(", ") rustTemplate( """ /// The service builder for [`$serviceName`]. /// - /// Constructed via [`$serviceName::builder_with_plugins`] or [`$serviceName::builder_without_plugins`]. + /// Constructed via [`$serviceName::builder`]. pub struct $builderName<$builderGenerics> { ${builderFields.joinToString(", ")}, + layer: L, http_plugin: HttpPl, model_plugin: ModelPl } @@ -445,14 +464,14 @@ class ServerServiceGenerator( } /** Returns a `Writable` comma delimited sequence of `builder_field: None`. */ - private val notSetFields = builderFieldNames.values.map { + private fun notSetFields(): Writable = builderFieldNames.values.map { writable { rustTemplate( "$it: None", *codegenScope, ) } - } + }.join(", ") /** Returns a `Writable` containing the service struct definition and its implementations. */ private fun serviceStruct(): Writable = writable { @@ -463,11 +482,31 @@ class ServerServiceGenerator( /// /// See the [root](crate) documentation for more information. ##[derive(Clone)] - pub struct $serviceName { - router: #{SmithyHttpServer}::routing::RoutingService<#{Router}, #{Protocol}>, + pub struct $serviceName { + // This is the router wrapped by layers. + svc: S, } - + impl $serviceName<()> { + /// Constructs a builder for [`$serviceName`]. + /// You must specify a configuration object holding any plugins and layers that should be applied + /// to the operations in this service. + pub fn builder< + Body, + L, + HttpPl: #{SmithyHttpServer}::plugin::HttpMarker, + ModelPl: #{SmithyHttpServer}::plugin::ModelMarker, + >( + config: ${serviceName}Config, + ) -> $builderName { + $builderName { + #{NotSetFields1:W}, + layer: config.layers, + http_plugin: config.http_plugins, + model_plugin: config.model_plugins, + } + } + /// Constructs a builder for [`$serviceName`]. /// You must specify what plugins should be applied to the operations in this service. /// @@ -476,9 +515,21 @@ class ServerServiceGenerator( /// Check out [`HttpPlugins`](#{SmithyHttpServer}::plugin::HttpPlugins) and /// [`ModelPlugins`](#{SmithyHttpServer}::plugin::ModelPlugins) if you need to apply /// multiple plugins. - pub fn builder_with_plugins(http_plugin: HttpPl, model_plugin: ModelPl) -> $builderName { + ##[deprecated( + since = "0.57.0", + note = "please use the `builder` constructor and register plugins on the `${serviceName}Config` object instead; see https://github.com/awslabs/smithy-rs/discussions/3096" + )] + pub fn builder_with_plugins< + Body, + HttpPl: #{SmithyHttpServer}::plugin::HttpMarker, + ModelPl: #{SmithyHttpServer}::plugin::ModelMarker + >( + http_plugin: HttpPl, + model_plugin: ModelPl + ) -> $builderName { $builderName { - #{NotSetFields:W}, + #{NotSetFields2:W}, + layer: #{Tower}::layer::util::Identity::new(), http_plugin, model_plugin } @@ -487,7 +538,16 @@ class ServerServiceGenerator( /// Constructs a builder for [`$serviceName`]. /// /// Use [`$serviceName::builder_with_plugins`] if you need to specify plugins. - pub fn builder_without_plugins() -> $builderName { + ##[deprecated( + since = "0.57.0", + note = "please use the `builder` constructor instead; see https://github.com/awslabs/smithy-rs/discussions/3096" + )] + pub fn builder_without_plugins() -> $builderName< + Body, + #{Tower}::layer::util::Identity, + #{SmithyHttpServer}::plugin::IdentityPlugin, + #{SmithyHttpServer}::plugin::IdentityPlugin + > { Self::builder_with_plugins(#{SmithyHttpServer}::plugin::IdentityPlugin, #{SmithyHttpServer}::plugin::IdentityPlugin) } } @@ -503,53 +563,85 @@ class ServerServiceGenerator( pub fn into_make_service_with_connect_info(self) -> #{SmithyHttpServer}::routing::IntoMakeServiceWithConnectInfo { #{SmithyHttpServer}::routing::IntoMakeServiceWithConnectInfo::new(self) } - + } + + impl + $serviceName< + #{SmithyHttpServer}::routing::RoutingService< + #{Router}, + #{Protocol}, + >, + > + { /// Applies a [`Layer`](#{Tower}::Layer) uniformly to all routes. - pub fn layer(self, layer: &L) -> $serviceName + ##[deprecated( + since = "0.57.0", + note = "please add layers to the `${serviceName}Config` object instead; see https://github.com/awslabs/smithy-rs/discussions/3096" + )] + pub fn layer( + self, + layer: &L, + ) -> $serviceName< + #{SmithyHttpServer}::routing::RoutingService< + #{Router}, + #{Protocol}, + >, + > where - L: #{Tower}::Layer + L: #{Tower}::Layer, { $serviceName { - router: self.router.map(|s| s.layer(layer)) + svc: self.svc.map(|s| s.layer(layer)), } } /// Applies [`Route::new`](#{SmithyHttpServer}::routing::Route::new) to all routes. /// - /// This has the effect of erasing all types accumulated via [`layer`]($serviceName::layer). - pub fn boxed(self) -> $serviceName<#{SmithyHttpServer}::routing::Route> + /// This has the effect of erasing all types accumulated via layers. + pub fn boxed( + self, + ) -> $serviceName< + #{SmithyHttpServer}::routing::RoutingService< + #{Router}< + #{SmithyHttpServer}::routing::Route, + >, + #{Protocol}, + >, + > where S: #{Tower}::Service< #{Http}::Request, Response = #{Http}::Response<#{SmithyHttpServer}::body::BoxBody>, - Error = std::convert::Infallible>, + Error = std::convert::Infallible, + >, S: Clone + Send + 'static, S::Future: Send + 'static, { - self.layer(&#{Tower}::layer::layer_fn(#{SmithyHttpServer}::routing::Route::new)) + self.layer(&::tower::layer::layer_fn( + #{SmithyHttpServer}::routing::Route::new, + )) } } - impl #{Tower}::Service<#{Http}::Request> for $serviceName + impl #{Tower}::Service for $serviceName where - S: #{Tower}::Service<#{Http}::Request, Response = #{Http}::Response> + Clone, - RespB: #{HttpBody}::Body + Send + 'static, - RespB::Error: Into> + S: #{Tower}::Service, { - type Response = #{Http}::Response<#{SmithyHttpServer}::body::BoxBody>; + type Response = S::Response; type Error = S::Error; - type Future = #{SmithyHttpServer}::routing::RoutingFuture; + type Future = S::Future; fn poll_ready(&mut self, cx: &mut std::task::Context) -> std::task::Poll> { - self.router.poll_ready(cx) + self.svc.poll_ready(cx) } - fn call(&mut self, request: #{Http}::Request) -> Self::Future { - self.router.call(request) + fn call(&mut self, request: R) -> Self::Future { + self.svc.call(request) } } """, - "NotSetFields" to notSetFields.join(", "), + "NotSetFields1" to notSetFields(), + "NotSetFields2" to notSetFields(), "Router" to protocol.routerType(), "Protocol" to protocol.markerStruct(), *codegenScope, @@ -598,7 +690,7 @@ class ServerServiceGenerator( val version = codegenContext.serviceShape.version?.let { "Some(\"$it\")" } ?: "None" rustTemplate( """ - impl #{SmithyHttpServer}::service::ServiceShape for $serviceName { + impl #{SmithyHttpServer}::service::ServiceShape for $serviceName { const ID: #{SmithyHttpServer}::shape_id::ShapeId = #{SmithyHttpServer}::shape_id::ShapeId::new("$absolute", "$namespace", "$name"); const VERSION: Option<&'static str> = $version; @@ -651,7 +743,9 @@ class ServerServiceGenerator( for ((_, value) in operationStructNames) { rustTemplate( """ - impl #{SmithyHttpServer}::service::ContainsOperation for $serviceName { + impl #{SmithyHttpServer}::service::ContainsOperation + for $serviceName + { const VALUE: Operation = Operation::$value; } """, diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServiceConfigGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServiceConfigGenerator.kt new file mode 100644 index 0000000000..960f9d0df7 --- /dev/null +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServiceConfigGenerator.kt @@ -0,0 +1,144 @@ +/* + * 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.rust.codegen.core.rustlang.RustWriter +import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate +import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType +import software.amazon.smithy.rust.codegen.core.util.toPascalCase +import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency +import software.amazon.smithy.rust.codegen.server.smithy.ServerCodegenContext + +class ServiceConfigGenerator( + codegenContext: ServerCodegenContext, +) { + private val crateName = codegenContext.moduleUseName() + private val codegenScope = codegenContext.runtimeConfig.let { runtimeConfig -> + val smithyHttpServer = ServerCargoDependency.smithyHttpServer(runtimeConfig).toType() + arrayOf( + "Debug" to RuntimeType.Debug, + "SmithyHttpServer" to smithyHttpServer, + "PluginStack" to smithyHttpServer.resolve("plugin::PluginStack"), + "ModelMarker" to smithyHttpServer.resolve("plugin::ModelMarker"), + "HttpMarker" to smithyHttpServer.resolve("plugin::HttpMarker"), + "Tower" to RuntimeType.Tower, + "Stack" to RuntimeType.Tower.resolve("layer::util::Stack"), + ) + } + private val serviceName = codegenContext.serviceShape.id.name.toPascalCase() + + fun render(writer: RustWriter) { + writer.rustTemplate( + """ + /// Configuration for the [`$serviceName`]. This is the central place where to register and + /// configure [`#{Tower}::Layer`]s, HTTP plugins, and model plugins. + /// + /// ```rust,no_run + /// ## use $crateName::${serviceName}Config; + /// ## use #{SmithyHttpServer}::plugin::IdentityPlugin; + /// ## use #{Tower}::layer::util::Identity; + /// ## let authentication_plugin = IdentityPlugin; + /// ## let authorization_plugin = IdentityPlugin; + /// ## let server_request_id_provider_layer = Identity::new(); + /// let config = ${serviceName}Config::builder() + /// // Layers get executed first... + /// .layer(server_request_id_provider_layer) + /// // ...then HTTP plugins... + /// .http_plugin(authentication_plugin) + /// // ...and right after deserialization, model plugins. + /// .model_plugin(authorization_plugin) + /// .build(); + /// ``` + /// + /// See the [`plugin`] system for details. + /// + /// [`plugin`]: #{SmithyHttpServer}::plugin + ##[derive(#{Debug})] + pub struct ${serviceName}Config { + layers: L, + http_plugins: H, + model_plugins: M, + } + + impl ${serviceName}Config<(), (), ()> { + /// Returns a builder to construct the configuration. + pub fn builder() -> ${serviceName}ConfigBuilder< + #{Tower}::layer::util::Identity, + #{SmithyHttpServer}::plugin::IdentityPlugin, + #{SmithyHttpServer}::plugin::IdentityPlugin, + > { + ${serviceName}ConfigBuilder { + layers: #{Tower}::layer::util::Identity::new(), + http_plugins: #{SmithyHttpServer}::plugin::IdentityPlugin, + model_plugins: #{SmithyHttpServer}::plugin::IdentityPlugin, + } + } + } + + /// Builder returned by [`${serviceName}Config::builder()`]. + ##[derive(#{Debug})] + pub struct ${serviceName}ConfigBuilder { + pub(crate) layers: L, + pub(crate) http_plugins: H, + pub(crate) model_plugins: M, + } + + impl ${serviceName}ConfigBuilder { + /// Add a [`#{Tower}::Layer`] to the service. + pub fn layer(self, layer: NewLayer) -> ${serviceName}ConfigBuilder<#{Stack}, H, M> { + ${serviceName}ConfigBuilder { + layers: #{Stack}::new(layer, self.layers), + http_plugins: self.http_plugins, + model_plugins: self.model_plugins, + } + } + + /// Add a HTTP [plugin] to the service. + /// + /// [plugin]: #{SmithyHttpServer}::plugin + // We eagerly require `NewPlugin: HttpMarker`, despite not really needing it, because compiler + // errors get _substantially_ better if the user makes a mistake. + pub fn http_plugin( + self, + http_plugin: NewPlugin, + ) -> ${serviceName}ConfigBuilder, M> { + ${serviceName}ConfigBuilder { + layers: self.layers, + http_plugins: #{PluginStack}::new(http_plugin, self.http_plugins), + model_plugins: self.model_plugins, + } + } + + /// Add a model [plugin] to the service. + /// + /// [plugin]: #{SmithyHttpServer}::plugin + // We eagerly require `NewPlugin: ModelMarker`, despite not really needing it, because compiler + // errors get _substantially_ better if the user makes a mistake. + pub fn model_plugin( + self, + model_plugin: NewPlugin, + ) -> ${serviceName}ConfigBuilder> { + ${serviceName}ConfigBuilder { + layers: self.layers, + http_plugins: self.http_plugins, + model_plugins: #{PluginStack}::new(model_plugin, self.model_plugins), + } + } + + /// Build the configuration. + pub fn build(self) -> super::${serviceName}Config { + super::${serviceName}Config { + layers: self.layers, + http_plugins: self.http_plugins, + model_plugins: self.model_plugins, + } + } + } + """, + *codegenScope, + ) + } +} diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt index 4327c8de20..c72b99575a 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt @@ -467,7 +467,8 @@ class ServerProtocolTestGenerator( """ ##[allow(unused_mut)] let (sender, mut receiver) = #{Tokio}::sync::mpsc::channel(1); - let service = crate::service::$serviceName::builder_without_plugins::<#{Hyper}::body::Body>() + let config = crate::service::${serviceName}Config::builder().build(); + let service = crate::service::$serviceName::builder::<#{Hyper}::body::Body, _, _, _>(config) .$operationName(move |input: $inputT| { let sender = sender.clone(); async move { diff --git a/design/src/server/anatomy.md b/design/src/server/anatomy.md index d15f4fbcc0..5146202877 100644 --- a/design/src/server/anatomy.md +++ b/design/src/server/anatomy.md @@ -41,24 +41,27 @@ service PokemonService { Smithy Rust will use this model to produce the following API: -```rust +```rust,no_run # extern crate pokemon_service_server_sdk; # extern crate aws_smithy_http_server; -# use pokemon_service_server_sdk::{input::*, output::*, error::*, operation_shape::*, PokemonService}; +# use aws_smithy_http_server::protocol::rest_json_1::{RestJson1, router::RestRouter}; +# use aws_smithy_http_server::routing::{Route, RoutingService}; +# use pokemon_service_server_sdk::{input::*, output::*, error::*, operation_shape::*, PokemonServiceConfig, PokemonService}; // A handler for the `GetPokemonSpecies` operation (the `PokemonSpecies` resource). async fn get_pokemon_species(input: GetPokemonSpeciesInput) -> Result { todo!() } +let config = PokemonServiceConfig::builder().build(); + // Use the service builder to create `PokemonService`. -let pokemon_service = PokemonService::builder_without_plugins() +let pokemon_service = PokemonService::builder(config) // Pass the handler directly to the service builder... .get_pokemon_species(get_pokemon_species) /* other operation setters */ .build() - # ; Result::<(), ()>::Ok(()) .expect("failed to create an instance of the Pokémon service"); -# let pokemon_service: Result, _> = pokemon_service; +# let pokemon_service: PokemonService, RestJson1>> = pokemon_service; ``` ## Operations @@ -463,7 +466,9 @@ stateDiagram-v2 S --> [*]: HTTP Response ``` -The service builder API requires plugins to be specified upfront - they must be passed as an argument to `builder_with_plugins` and cannot be modified afterwards. +The service builder API requires plugins to be specified upfront - they must be +registered in the config object, which is passed as an argument to `builder`. +Plugins cannot be modified afterwards. You might find yourself wanting to apply _multiple_ plugins to your service. This can be accommodated via [`HttpPlugins`] and [`ModelPlugins`]. @@ -510,7 +515,7 @@ let http_plugins = HttpPlugins::new() The service builder is the primary public API, generated for every [Smithy Service](https://awslabs.github.io/smithy/2.0/spec/service-types.html). At a high-level, the service builder takes as input a function for each Smithy Operation and returns a single HTTP service. The signature of each function, also known as _handlers_, must match the constraints of the corresponding Smithy model. -You can create an instance of a service builder by calling either `builder_without_plugins` or `builder_with_plugins` on the corresponding service struct. +You can create an instance of a service builder by calling `builder` on the corresponding service struct. ```rust # extern crate aws_smithy_http_server; diff --git a/design/src/server/instrumentation.md b/design/src/server/instrumentation.md index 08c72a2098..ac5c2c56a4 100644 --- a/design/src/server/instrumentation.md +++ b/design/src/server/instrumentation.md @@ -68,15 +68,18 @@ use aws_smithy_http_server::{ instrumentation::InstrumentExt, plugin::{IdentityPlugin, HttpPlugins} }; -use pokemon_service_server_sdk::PokemonService; +# use aws_smithy_http_server::protocol::rest_json_1::{RestJson1, router::RestRouter}; +# use aws_smithy_http_server::routing::{Route, RoutingService}; +use pokemon_service_server_sdk::{PokemonServiceConfig, PokemonService}; let http_plugins = HttpPlugins::new().instrument(); -let app = PokemonService::builder_with_plugins(http_plugins, IdentityPlugin) +let config = PokemonServiceConfig::builder().http_plugin(http_plugins).build(); +let app = PokemonService::builder(config) .get_pokemon_species(handler) /* ... */ .build() .unwrap(); -# let app: PokemonService = app; +# let app: PokemonService, RestJson1>> = app; ``` diff --git a/design/src/server/middleware.md b/design/src/server/middleware.md index af394b1bca..589fb0770c 100644 --- a/design/src/server/middleware.md +++ b/design/src/server/middleware.md @@ -147,16 +147,20 @@ The output of the Smithy service builder provides the user with a `Service Layer for TimeoutLayer { type Service = S; fn layer(&self, svc: S) -> Self::Service { svc } } # use pokemon_service_server_sdk::{input::*, output::*, error::*}; # let handler = |req: GetPokemonSpeciesInput| async { Result::::Ok(todo!()) }; -use pokemon_service_server_sdk::PokemonService; +# use aws_smithy_http_server::protocol::rest_json_1::{RestJson1, router::RestRouter}; +# use aws_smithy_http_server::routing::{Route, RoutingService}; +use pokemon_service_server_sdk::{PokemonServiceConfig, PokemonService}; use tower::Layer; +let config = PokemonServiceConfig::builder().build(); + // This is a HTTP `Service`. -let app /* : PokemonService> */ = PokemonService::builder_without_plugins() +let app = PokemonService::builder(config) .get_pokemon_species(handler) /* ... */ .build() .unwrap(); -# let app: PokemonService = app; +# let app: PokemonService, RestJson1>> = app; // Construct `TimeoutLayer`. let timeout_layer = TimeoutLayer::new(Duration::from_secs(3)); @@ -167,7 +171,9 @@ let app = timeout_layer.layer(app); ### B. Route Middleware -A _single_ layer can be applied to _all_ routes inside the `Router`. This exists as a method on the output of the service builder. +A _single_ layer can be applied to _all_ routes inside the `Router`. This +exists as a method on the `PokemonServiceConfig` builder object, which is passed into the +service builder. ```rust,no_run # extern crate tower; @@ -175,25 +181,26 @@ A _single_ layer can be applied to _all_ routes inside the `Router`. This exists # extern crate aws_smithy_http_server; # use tower::{util::service_fn, Layer}; # use std::time::Duration; +# use aws_smithy_http_server::protocol::rest_json_1::{RestJson1, router::RestRouter}; +# use aws_smithy_http_server::routing::{Route, RoutingService}; # use pokemon_service_server_sdk::{input::*, output::*, error::*}; # let handler = |req: GetPokemonSpeciesInput| async { Result::::Ok(todo!()) }; # struct MetricsLayer; # impl MetricsLayer { pub fn new() -> Self { Self } } # impl Layer for MetricsLayer { type Service = S; fn layer(&self, svc: S) -> Self::Service { svc } } -use pokemon_service_server_sdk::PokemonService; +use pokemon_service_server_sdk::{PokemonService, PokemonServiceConfig}; // Construct `MetricsLayer`. let metrics_layer = MetricsLayer::new(); -let app /* : PokemonService> */ = PokemonService::builder_without_plugins() +let config = PokemonServiceConfig::builder().layer(metrics_layer).build(); + +let app = PokemonService::builder(config) .get_pokemon_species(handler) /* ... */ .build() - .unwrap() - # ; let app: PokemonService = app; - # app - // Apply HTTP logging after routing. - .layer(&metrics_layer); + .unwrap(); +# let app: PokemonService, RestJson1>> = app; ``` Note that requests pass through this middleware immediately _after_ routing succeeds and therefore will _not_ be encountered if routing fails. This means that the [TraceLayer](https://docs.rs/tower-http/latest/tower_http/trace/struct.TraceLayer.html) in the example above does _not_ provide logs unless routing has completed. This contrasts to [middleware A](#a-outer-middleware), which _all_ requests/responses pass through when entering/leaving the service. @@ -209,12 +216,14 @@ A "HTTP layer" can be applied to specific operations. # use tower::{util::service_fn, Layer}; # use std::time::Duration; # use pokemon_service_server_sdk::{operation_shape::GetPokemonSpecies, input::*, output::*, error::*}; +# use aws_smithy_http_server::protocol::rest_json_1::{RestJson1, router::RestRouter}; +# use aws_smithy_http_server::routing::{Route, RoutingService}; # use aws_smithy_http_server::{operation::OperationShapeExt, plugin::*, operation::*}; # let handler = |req: GetPokemonSpeciesInput| async { Result::::Ok(todo!()) }; # struct LoggingLayer; # impl LoggingLayer { pub fn new() -> Self { Self } } # impl Layer for LoggingLayer { type Service = S; fn layer(&self, svc: S) -> Self::Service { svc } } -use pokemon_service_server_sdk::{PokemonService, scope}; +use pokemon_service_server_sdk::{PokemonService, PokemonServiceConfig, scope}; scope! { /// Only log on `GetPokemonSpecies` and `GetStorage` @@ -228,12 +237,14 @@ let logging_plugin = LayerPlugin(LoggingLayer::new()); let logging_plugin = Scoped::new::(logging_plugin); let http_plugins = HttpPlugins::new().push(logging_plugin); -let app /* : PokemonService> */ = PokemonService::builder_with_plugins(http_plugins, IdentityPlugin) +let config = PokemonServiceConfig::builder().http_plugin(http_plugins).build(); + +let app = PokemonService::builder(config) .get_pokemon_species(handler) /* ... */ .build() .unwrap(); -# let app: PokemonService = app; +# let app: PokemonService, RestJson1>> = app; ``` This middleware transforms the operations HTTP requests and responses. @@ -250,10 +261,12 @@ A "model layer" can be applied to specific operations. # use pokemon_service_server_sdk::{operation_shape::GetPokemonSpecies, input::*, output::*, error::*}; # let handler = |req: GetPokemonSpeciesInput| async { Result::::Ok(todo!()) }; # use aws_smithy_http_server::{operation::*, plugin::*}; +# use aws_smithy_http_server::protocol::rest_json_1::{RestJson1, router::RestRouter}; +# use aws_smithy_http_server::routing::{Route, RoutingService}; # struct BufferLayer; # impl BufferLayer { pub fn new(size: usize) -> Self { Self } } # impl Layer for BufferLayer { type Service = S; fn layer(&self, svc: S) -> Self::Service { svc } } -use pokemon_service_server_sdk::{PokemonService, scope}; +use pokemon_service_server_sdk::{PokemonService, PokemonServiceConfig, scope}; scope! { /// Only buffer on `GetPokemonSpecies` and `GetStorage` @@ -265,14 +278,14 @@ scope! { // Construct `BufferLayer`. let buffer_plugin = LayerPlugin(BufferLayer::new(3)); let buffer_plugin = Scoped::new::(buffer_plugin); -let model_plugins = ModelPlugins::new().push(buffer_plugin); +let config = PokemonServiceConfig::builder().model_plugin(buffer_plugin).build(); -let app /* : PokemonService> */ = PokemonService::builder_with_plugins(IdentityPlugin, model_plugins) +let app = PokemonService::builder(config) .get_pokemon_species(handler) /* ... */ .build() .unwrap(); -# let app: PokemonService = app; +# let app: PokemonService, RestJson1>> = app; ``` In contrast to [position C](#c-operation-specific-http-middleware), this middleware transforms the operations modelled inputs to modelled outputs. @@ -283,7 +296,7 @@ Suppose we want to apply a different `Layer` to every operation. In this case, p Consider the following middleware: -```rust +```rust,no_run # extern crate aws_smithy_http_server; # extern crate tower; use aws_smithy_http_server::shape_id::ShapeId; @@ -320,7 +333,7 @@ The plugin system provides a way to construct then apply `Layer`s in position [C An example of a `PrintPlugin` which prints the operation name: -```rust +```rust,no_run # extern crate aws_smithy_http_server; # use aws_smithy_http_server::shape_id::ShapeId; # pub struct PrintService { inner: S, operation_id: ShapeId, service_id: ShapeId } @@ -349,7 +362,7 @@ where You can provide a custom method to add your plugin to a collection of `HttpPlugins` or `ModelPlugins` via an extension trait. For example, for `HttpPlugins`: -```rust +```rust,no_run # extern crate aws_smithy_http_server; # pub struct PrintPlugin; # impl aws_smithy_http_server::plugin::HttpMarker for PrintPlugin { } @@ -383,19 +396,22 @@ This allows for: # impl PrintExt for HttpPlugins { fn print(self) -> HttpPlugins> { self.push(PrintPlugin) }} # use pokemon_service_server_sdk::{operation_shape::GetPokemonSpecies, input::*, output::*, error::*}; # let handler = |req: GetPokemonSpeciesInput| async { Result::::Ok(todo!()) }; +# use aws_smithy_http_server::protocol::rest_json_1::{RestJson1, router::RestRouter}; +# use aws_smithy_http_server::routing::{Route, RoutingService}; use aws_smithy_http_server::plugin::{IdentityPlugin, HttpPlugins}; -use pokemon_service_server_sdk::PokemonService; +use pokemon_service_server_sdk::{PokemonService, PokemonServiceConfig}; let http_plugins = HttpPlugins::new() // [..other plugins..] // The custom method! .print(); -let app /* : PokemonService> */ = PokemonService::builder_with_plugins(http_plugins, IdentityPlugin) +let config = PokemonServiceConfig::builder().http_plugin(http_plugins).build(); +let app /* : PokemonService> */ = PokemonService::builder(config) .get_pokemon_species(handler) /* ... */ .build() .unwrap(); -# let app: PokemonService = app; +# let app: PokemonService, RestJson1>> = app; ``` The custom `print` method hides the details of the `Plugin` trait from the average consumer. diff --git a/examples/pokemon-service-common/tests/plugins_execution_order.rs b/examples/pokemon-service-common/tests/plugins_execution_order.rs index bf09387d37..4a7bb03751 100644 --- a/examples/pokemon-service-common/tests/plugins_execution_order.rs +++ b/examples/pokemon-service-common/tests/plugins_execution_order.rs @@ -10,7 +10,8 @@ use std::{ task::{Context, Poll}, }; -use aws_smithy_http_server::plugin::{HttpMarker, HttpPlugins, IdentityPlugin, Plugin}; +use aws_smithy_http_server::plugin::{HttpMarker, HttpPlugins, Plugin}; +use pokemon_service_server_sdk::{PokemonService, PokemonServiceConfig}; use tower::{Layer, Service}; use aws_smithy_runtime::client::http::test_util::capture_request; @@ -26,12 +27,12 @@ async fn plugin_layers_are_executed_in_registration_order() { let http_plugins = HttpPlugins::new() .push(SentinelPlugin::new("first", output.clone())) .push(SentinelPlugin::new("second", output.clone())); - let mut app = pokemon_service_server_sdk::PokemonService::builder_with_plugins( - http_plugins, - IdentityPlugin, - ) - .do_nothing(do_nothing) - .build_unchecked(); + let config = PokemonServiceConfig::builder() + .http_plugin(http_plugins) + .build(); + let mut app = PokemonService::builder(config) + .do_nothing(do_nothing) + .build_unchecked(); let request = { let (http_client, rcvr) = capture_request(None); @@ -74,7 +75,6 @@ impl Plugin for SentinelPlugin { impl HttpMarker for SentinelPlugin {} -/// A [`Service`] that adds a print log. #[derive(Clone, Debug)] pub struct SentinelService { inner: S, @@ -100,7 +100,6 @@ where } } -/// A [`Layer`] which constructs the [`PrintService`]. #[derive(Debug)] pub struct SentinelLayer { name: &'static str, diff --git a/examples/pokemon-service-lambda/src/main.rs b/examples/pokemon-service-lambda/src/main.rs index 2f08e072b5..6f58115866 100644 --- a/examples/pokemon-service-lambda/src/main.rs +++ b/examples/pokemon-service-lambda/src/main.rs @@ -12,13 +12,17 @@ use pokemon_service_common::{ setup_tracing, stream_pokemon_radio, State, }; use pokemon_service_lambda::get_storage_lambda; -use pokemon_service_server_sdk::PokemonService; +use pokemon_service_server_sdk::{PokemonService, PokemonServiceConfig}; #[tokio::main] pub async fn main() { setup_tracing(); - let app = PokemonService::builder_without_plugins() + let config = PokemonServiceConfig::builder() + // Set up shared state and middlewares. + .layer(AddExtensionLayer::new(Arc::new(State::default()))) + .build(); + let app = PokemonService::builder(config) // Build a registry containing implementations to all the operations in the service. These // are async functions or async closures that take as input the operation's input and // return the operation's output. @@ -30,9 +34,7 @@ pub async fn main() { .check_health(check_health) .stream_pokemon_radio(stream_pokemon_radio) .build() - .expect("failed to build an instance of PokemonService") - // Set up shared state and middlewares. - .layer(&AddExtensionLayer::new(Arc::new(State::default()))); + .expect("failed to build an instance of PokemonService"); let handler = LambdaHandler::new(app); let lambda = lambda_http::run(handler); diff --git a/examples/pokemon-service-tls/src/main.rs b/examples/pokemon-service-tls/src/main.rs index a67d145042..45d6964686 100644 --- a/examples/pokemon-service-tls/src/main.rs +++ b/examples/pokemon-service-tls/src/main.rs @@ -36,7 +36,7 @@ use pokemon_service_common::{ capture_pokemon, check_health, do_nothing, get_pokemon_species, get_server_statistics, get_storage, setup_tracing, stream_pokemon_radio, State, }; -use pokemon_service_server_sdk::PokemonService; +use pokemon_service_server_sdk::{PokemonService, PokemonServiceConfig}; use pokemon_service_tls::{DEFAULT_ADDRESS, DEFAULT_PORT, DEFAULT_TEST_CERT, DEFAULT_TEST_KEY}; #[derive(Parser, Debug)] @@ -61,7 +61,11 @@ pub async fn main() { let args = Args::parse(); setup_tracing(); - let app = PokemonService::builder_without_plugins() + let config = PokemonServiceConfig::builder() + // Set up shared state and middlewares. + .layer(AddExtensionLayer::new(Arc::new(State::default()))) + .build(); + let app = PokemonService::builder(config) // Build a registry containing implementations to all the operations in the service. These // are async functions or async closures that take as input the operation's input and // return the operation's output. @@ -73,9 +77,7 @@ pub async fn main() { .check_health(check_health) .stream_pokemon_radio(stream_pokemon_radio) .build() - .expect("failed to build an instance of PokemonService") - // Set up shared state and middlewares. - .layer(&AddExtensionLayer::new(Arc::new(State::default()))); + .expect("failed to build an instance of PokemonService"); let addr: SocketAddr = format!("{}:{}", args.address, args.port) .parse() diff --git a/examples/pokemon-service/src/main.rs b/examples/pokemon-service/src/main.rs index b4198fdf3d..97d93378ff 100644 --- a/examples/pokemon-service/src/main.rs +++ b/examples/pokemon-service/src/main.rs @@ -28,7 +28,7 @@ use pokemon_service_common::{ capture_pokemon, check_health, get_pokemon_species, get_server_statistics, setup_tracing, stream_pokemon_radio, State, }; -use pokemon_service_server_sdk::{scope, PokemonService}; +use pokemon_service_server_sdk::{scope, PokemonService, PokemonServiceConfig}; use crate::authz::AuthorizationPlugin; @@ -49,15 +49,15 @@ pub async fn main() { setup_tracing(); scope! { - /// A scope containing `GetPokemonSpecies` and `GetStorage` + /// A scope containing `GetPokemonSpecies` and `GetStorage`. struct PrintScope { includes: [GetPokemonSpecies, GetStorage] } } - // Scope the `PrintPlugin`, defined in `plugin.rs`, to `PrintScope` + // Scope the `PrintPlugin`, defined in `plugin.rs`, to `PrintScope`. let print_plugin = Scoped::new::(HttpPlugins::new().print()); - let plugins = HttpPlugins::new() + let http_plugins = HttpPlugins::new() // Apply the scoped `PrintPlugin` .push(print_plugin) // Apply the `OperationExtensionPlugin` defined in `aws_smithy_http_server::extension`. This allows other @@ -70,7 +70,20 @@ pub async fn main() { let authz_plugin = AuthorizationPlugin::new(); let model_plugins = ModelPlugins::new().push(authz_plugin); - let app = PokemonService::builder_with_plugins(plugins, model_plugins) + let config = PokemonServiceConfig::builder() + // Set up shared state and middlewares. + .layer(AddExtensionLayer::new(Arc::new(State::default()))) + // Handle `/ping` health check requests. + .layer(AlbHealthCheckLayer::from_handler("/ping", |_req| async { + StatusCode::OK + })) + // Add server request IDs. + .layer(ServerRequestIdProviderLayer::new()) + .http_plugin(http_plugins) + .model_plugin(model_plugins) + .build(); + + let app = PokemonService::builder(config) // Build a registry containing implementations to all the operations in the service. These // are async functions or async closures that take as input the operation's input and // return the operation's output. @@ -84,16 +97,6 @@ pub async fn main() { .build() .expect("failed to build an instance of PokemonService"); - let app = app - // Setup shared state and middlewares. - .layer(&AddExtensionLayer::new(Arc::new(State::default()))) - // Handle `/ping` health check requests. - .layer(&AlbHealthCheckLayer::from_handler("/ping", |_req| async { - StatusCode::OK - })) - // Add server request IDs. - .layer(&ServerRequestIdProviderLayer::new()); - // Using `into_make_service_with_connect_info`, rather than `into_make_service`, to adjoin the `SocketAddr` // connection info. let make_app = app.into_make_service_with_connect_info::(); diff --git a/rust-runtime/aws-smithy-http-server/src/plugin/http_plugins.rs b/rust-runtime/aws-smithy-http-server/src/plugin/http_plugins.rs index 63aece88a6..c8369a66bf 100644 --- a/rust-runtime/aws-smithy-http-server/src/plugin/http_plugins.rs +++ b/rust-runtime/aws-smithy-http-server/src/plugin/http_plugins.rs @@ -11,8 +11,6 @@ use crate::plugin::{IdentityPlugin, Plugin, PluginStack}; use super::{HttpMarker, LayerPlugin}; /// A wrapper struct for composing HTTP plugins. -/// It can be used as input for the `builder_with_plugins` method on the generated service struct -/// (e.g. `PokemonService::builder_with_plugins`). /// /// ## Applying plugins in a sequence /// diff --git a/rust-runtime/aws-smithy-http-server/src/request/request_id.rs b/rust-runtime/aws-smithy-http-server/src/request/request_id.rs index 7d6ee70ab7..a97288841c 100644 --- a/rust-runtime/aws-smithy-http-server/src/request/request_id.rs +++ b/rust-runtime/aws-smithy-http-server/src/request/request_id.rs @@ -32,13 +32,14 @@ //! todo!() //! } //! -//! let app = Service::builder_without_plugins() +//! let config = ServiceConfig::builder() +//! // Generate a server request ID and add it to the response header. +//! .layer(ServerRequestIdProviderLayer::new_with_response_header(HeaderName::from_static("x-request-id"))) +//! .build(); +//! let app = Service::builder(config) //! .operation(handler) //! .build().unwrap(); //! -//! let app = app -//! .layer(&ServerRequestIdProviderLayer::new_with_response_header(HeaderName::from_static("x-request-id"))); /* Generate a server request ID and add it to the response header */ -//! //! let bind: std::net::SocketAddr = format!("{}:{}", args.address, args.port) //! .parse() //! .expect("unable to parse the server bind address and port");