From 783b50a40db1dcead6ef5ea42dbf3698d8fbff32 Mon Sep 17 00:00:00 2001 From: david-perez Date: Mon, 24 Jul 2023 17:00:42 +0200 Subject: [PATCH] Service config --- Cargo.lock | 2 + sdk/Cargo.toml | 2 +- sdk/src/lib.rs | 82 ++++++++------ sdk/src/service.rs | 252 ++++++++++++++++++++++++++------------------ service/Cargo.toml | 2 + service/src/main.rs | 13 ++- 6 files changed, 216 insertions(+), 137 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0dad60f..4113e3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -585,6 +585,8 @@ dependencies = [ name = "service" version = "0.1.0" dependencies = [ + "aws-smithy-http-server", + "http", "hyper", "simple", "tokio", diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 07c2f1b..a9a182e 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -7,7 +7,7 @@ description = "test" edition = "2021" [package.metadata.smithy] -codegen-version = "0.0.0-smithy-rs-head-e060135e0176677504c1d17dbbc5e6556aeee0ca" +codegen-version = "0.0.0-smithy-rs-head-50dbc1f023b082b028293b9dbde16a16865ee404" [dependencies.aws-smithy-http] path = "/home/ANT.AMAZON.COM/davidpz/code/smithy-rs-main/rust-runtime/aws-smithy-http" [dependencies.aws-smithy-http-server] diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index f929288..c68c232 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -31,7 +31,8 @@ //! # async fn dummy() { //! use simple::SimpleService; //! -//! # let app = SimpleService::builder_without_plugins().build_unchecked(); +//! # let config = simple::Config::builder().build(); +//! # let app = SimpleService::builder(config).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"); @@ -41,14 +42,16 @@ //! //! ### Running on Lambda //! -//! This requires the `aws-lambda` feature flag to be passed to the [`::aws_smithy_http_server`] crate. +//! This requires the `aws-lambda` feature flag to be enabled in the [`::aws_smithy_http_server`] crate. //! //! ```rust,ignore //! use ::aws_smithy_http_server::routing::LambdaHandler; //! use simple::SimpleService; +//! # use simple::Config; //! //! # async fn dummy() { -//! # let app = SimpleService::builder_without_plugins().build_unchecked(); +//! # let config = simple::Config::builder().build(); +//! # let app = SimpleService::builder(config).build_unchecked(); //! let handler = LambdaHandler::new(app); //! lambda_http::run(handler).await.unwrap(); //! # } @@ -56,28 +59,40 @@ //! //! # Building the SimpleService //! -//! To construct [`SimpleService`] we use [`SimpleServiceBuilder`] returned by [`SimpleService::builder_without_plugins`] -//! or [`SimpleService::builder_with_plugins`]. +//! Constructing the [`SimpleService`] entails two steps: //! -//! ## Plugins +//! 1. Building the service configration. +//! 2. Registering the handlers. //! -//! The [`SimpleService::builder_with_plugins`] method, returning [`SimpleServiceBuilder`], -//! accepts a plugin marked with [`HttpMarker`](aws_smithy_http_server::plugin::HttpMarker) and a -//! plugin marked with [`ModelMarker`](aws_smithy_http_server::plugin::ModelMarker). -//! Plugins allow you to build middleware which is aware of the operation it is being applied to. +//! To construct [`SimpleService`] we use [`SimpleServiceBuilder`] returned by +//! [`SimpleService::builder`], but we first need to pass in the configuration of the service using +//! [`Config`]. //! -//! ```rust +//! ## Service configuration +//! +//! You define your service's configuration using [`Config`], which you can build with +//! [`Config::builder`]. +//! +//! [`Config`] allows you to register: +//! 1. Layers that get enacted _before routing_. +//! 2. HTTP plugins that run after routing, but _before HTTP request deserialization_. +//! 3. Model plugins that run after routing, and _after HTTP request deserialization_. +//! +//! HTTP plugins are those marked with [`HttpMarker`](aws_smithy_http_server::plugin::HttpMarker), +//! while model plugins are those marked with +//! [`ModelMarker`](aws_smithy_http_server::plugin::ModelMarker). Plugins allow you to build +//! middleware which is aware of the operation it is being applied to. +//! +//! ```no_run //! # use ::aws_smithy_http_server::plugin::IdentityPlugin; //! # use ::aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin; //! # use ::aws_smithy_http_server::plugin::IdentityPlugin as MetricsPlugin; //! # use ::hyper::Body; //! use ::aws_smithy_http_server::plugin::HttpPlugins; -//! use simple::{SimpleService, SimpleServiceBuilder}; +//! use simple::{Config, SimpleService, SimpleServiceBuilder}; //! -//! let http_plugins = HttpPlugins::new() -//! .push(LoggingPlugin) -//! .push(MetricsPlugin); -//! let builder: SimpleServiceBuilder = SimpleService::builder_with_plugins(http_plugins, IdentityPlugin); +//! let config = Config::builder().http_plugin(LoggingPlugin).build(); +//! let builder: SimpleServiceBuilder = SimpleService::builder(config); //! ``` //! //! Check out [`::aws_smithy_http_server::plugin`] to learn more about plugins. @@ -143,24 +158,25 @@ //! //! ```rust //! # use std::net::SocketAddr; -//! use simple::SimpleService; +//! use simple::{Config, SimpleService}; //! //! #[::tokio::main] //! pub async fn main() { -//! let app = SimpleService::builder_without_plugins() -//! .operation(operation) -//! .build() -//! .expect("failed to build an instance of SimpleService"); -//! -//! let bind: SocketAddr = "127.0.0.1:6969".parse() -//! .expect("unable to parse the server bind address and port"); -//! let server = ::hyper::Server::bind(&bind).serve(app.into_make_service()); -//! # let server = async { Ok::<_, ()>(()) }; -//! -//! // Run your service! -//! if let Err(err) = server.await { -//! eprintln!("server error: {:?}", err); -//! } +//! let config = Config::builder().build(); +//! let app = SimpleService::builder(config) +//! .operation(operation) +//! .build() +//! .expect("failed to build an instance of SimpleService"); +//! +//! let bind: SocketAddr = "127.0.0.1:6969".parse() +//! .expect("unable to parse the server bind address and port"); +//! let server = ::hyper::Server::bind(&bind).serve(app.into_make_service()); +//! # let server = async { Ok::<_, ()>(()) }; +//! +//! // Run your service! +//! if let Err(err) = server.await { +//! eprintln!("server error: {:?}", err); +//! } //! } //! //! use simple::{input, output}; @@ -177,7 +193,9 @@ //! [operations]: https://smithy.io/2.0/spec/service-types.html#operation //! [hyper server]: https://docs.rs/hyper/latest/hyper/server/index.html //! [Service]: https://docs.rs/tower-service/latest/tower_service/trait.Service.html -pub use crate::service::{MissingOperationsError, SimpleService, SimpleServiceBuilder}; +pub use crate::service::{ + config, Config, MissingOperationsError, SimpleService, SimpleServiceBuilder, +}; /// Crate version number. pub static PKG_VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/sdk/src/service.rs b/sdk/src/service.rs index f34175d..15106f0 100644 --- a/sdk/src/service.rs +++ b/sdk/src/service.rs @@ -2,13 +2,14 @@ /// The service builder for [`SimpleService`]. /// /// Constructed via [`SimpleService::builder_with_plugins`] or [`SimpleService::builder_without_plugins`]. -pub struct SimpleServiceBuilder { +pub struct SimpleServiceBuilder { operation: Option<::aws_smithy_http_server::routing::Route>, + layer: L, http_plugin: HttpPl, model_plugin: ModelPl, } -impl SimpleServiceBuilder { +impl SimpleServiceBuilder { /// Sets the [`Operation`](crate::operation_shape::Operation) operation. /// /// This should be an async function satisfying the [`Handler`](::aws_smithy_http_server::operation::Handler) trait. @@ -17,7 +18,7 @@ impl SimpleServiceBuilder { /// # Example /// /// ```no_run - /// use simple::SimpleService; + /// use simple::{Config, SimpleService}; /// /// use simple::{input, output}; /// @@ -25,12 +26,13 @@ impl SimpleServiceBuilder { /// todo!() /// } /// - /// let app = SimpleService::builder_without_plugins() + /// let config = Config::builder().build(); + /// let app = SimpleService::builder(config) /// .operation(handler) /// /* Set other handlers */ /// .build() /// .unwrap(); - /// # let app: SimpleService<::aws_smithy_http_server::routing::Route<::aws_smithy_http::body::SdkBody>> = app; + /// # let app: SimpleService<::aws_smithy_http_server::routing::RoutingService<::aws_smithy_http_server::protocol::rest::router::RestRouter<::aws_smithy_http_server::routing::Route>, ::aws_smithy_http_server::protocol::rest_json_1::RestJson1>> = app; /// ``` /// pub fn operation(self, handler: HandlerType) -> Self @@ -38,22 +40,22 @@ impl SimpleServiceBuilder { HandlerType: ::aws_smithy_http_server::operation::Handler, ModelPl: ::aws_smithy_http_server::plugin::Plugin< - SimpleService, + SimpleService, crate::operation_shape::Operation, ::aws_smithy_http_server::operation::IntoService >, ::aws_smithy_http_server::operation::UpgradePlugin::: ::aws_smithy_http_server::plugin::Plugin< - SimpleService, + SimpleService, crate::operation_shape::Operation, ModelPl::Output >, HttpPl: ::aws_smithy_http_server::plugin::Plugin< - SimpleService, + SimpleService, crate::operation_shape::Operation, < ::aws_smithy_http_server::operation::UpgradePlugin:: as ::aws_smithy_http_server::plugin::Plugin< - SimpleService, + SimpleService, crate::operation_shape::Operation, ModelPl::Output > @@ -82,7 +84,7 @@ impl SimpleServiceBuilder { /// # Example /// /// ```no_run - /// use simple::SimpleService; + /// use simple::{Config, SimpleService}; /// /// use simple::{input, output}; /// @@ -90,13 +92,14 @@ impl SimpleServiceBuilder { /// todo!() /// } /// + /// let config = Config::builder().build(); /// let svc = ::tower::util::service_fn(handler); - /// let app = SimpleService::builder_without_plugins() + /// let app = SimpleService::builder(config) /// .operation_service(svc) /// /* Set other handlers */ /// .build() /// .unwrap(); - /// # let app: SimpleService<::aws_smithy_http_server::routing::Route<::aws_smithy_http::body::SdkBody>> = app; + /// # let app: SimpleService<::aws_smithy_http_server::routing::RoutingService<::aws_smithy_http_server::protocol::rest::router::RestRouter<::aws_smithy_http_server::routing::Route>, ::aws_smithy_http_server::protocol::rest_json_1::RestJson1>> = app; /// ``` /// pub fn operation_service(self, service: S) -> Self @@ -104,22 +107,22 @@ impl SimpleServiceBuilder { S: ::aws_smithy_http_server::operation::OperationService, ModelPl: ::aws_smithy_http_server::plugin::Plugin< - SimpleService, + SimpleService, crate::operation_shape::Operation, ::aws_smithy_http_server::operation::Normalize >, ::aws_smithy_http_server::operation::UpgradePlugin::: ::aws_smithy_http_server::plugin::Plugin< - SimpleService, + SimpleService, crate::operation_shape::Operation, ModelPl::Output >, HttpPl: ::aws_smithy_http_server::plugin::Plugin< - SimpleService, + SimpleService, crate::operation_shape::Operation, < ::aws_smithy_http_server::operation::UpgradePlugin:: as ::aws_smithy_http_server::plugin::Plugin< - SimpleService, + SimpleService, crate::operation_shape::Operation, ModelPl::Output > @@ -158,16 +161,23 @@ impl SimpleServiceBuilder { } } -impl SimpleServiceBuilder { +impl SimpleServiceBuilder { /// Constructs a [`SimpleService`] from the arguments provided to the builder. /// /// Forgetting to register a handler for one or more operations will result in an error. /// /// Check out [`SimpleServiceBuilder::build_unchecked`] if you'd prefer the service to return status code 500 when an /// unspecified route requested. - pub fn build( - self, - ) -> Result>, MissingOperationsError> + pub fn build(self) -> Result, MissingOperationsError> + where + L: tower::Layer< + ::aws_smithy_http_server::routing::RoutingService< + ::aws_smithy_http_server::protocol::rest::router::RestRouter< + aws_smithy_http_server::routing::Route, + >, + ::aws_smithy_http_server::protocol::rest_json_1::RestJson1, + >, + >, { let router = { use ::aws_smithy_http_server::operation::OperationShape; @@ -188,9 +198,12 @@ impl SimpleServiceBuilder { self.operation.expect(unexpected_error_msg), )]) }; - Ok(SimpleService { - router: ::aws_smithy_http_server::routing::RoutingService::new(router), - }) + let svc = self + .layer + .layer(::aws_smithy_http_server::routing::RoutingService::new( + router, + )); + Ok(SimpleService { svc }) } /// Constructs a [`SimpleService`] from the arguments provided to the builder. @@ -198,9 +211,17 @@ impl SimpleServiceBuilder { /// /// Check out [`SimpleServiceBuilder::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) -> SimpleService<::aws_smithy_http_server::routing::Route> + pub fn build_unchecked(self) -> SimpleService where Body: Send + 'static, + L: tower::Layer< + ::aws_smithy_http_server::routing::RoutingService< + ::aws_smithy_http_server::protocol::rest::router::RestRouter< + aws_smithy_http_server::routing::Route, + >, + ::aws_smithy_http_server::protocol::rest_json_1::RestJson1, + >, + >, { let router = ::aws_smithy_http_server::protocol::rest::router::RestRouter::from_iter([( request_specs::operation(), @@ -211,9 +232,12 @@ impl SimpleServiceBuilder { ::aws_smithy_http_server::routing::Route::new(svc) }), )]); - SimpleService { - router: ::aws_smithy_http_server::routing::RoutingService::new(router), - } + let svc = self + .layer + .layer(::aws_smithy_http_server::routing::RoutingService::new( + router, + )); + SimpleService { svc } } } @@ -267,50 +291,27 @@ mod request_specs { /// /// See the [root](crate) documentation for more information. #[derive(Clone)] -pub struct SimpleService { - router: ::aws_smithy_http_server::routing::RoutingService< - ::aws_smithy_http_server::protocol::rest::router::RestRouter, - ::aws_smithy_http_server::protocol::rest_json_1::RestJson1, - >, +pub struct SimpleService { + // This is the router wrapped by layers. + svc: S, } impl SimpleService<()> { - /// Constructs a builder for [`SimpleService`]. - /// You must specify what plugins should be applied to the operations in this service. - /// - /// Use [`SimpleService::builder_without_plugins`] if you don't need to apply plugins. - /// - /// Check out [`HttpPlugins`](::aws_smithy_http_server::plugin::HttpPlugins) and - /// [`ModelPlugins`](::aws_smithy_http_server::plugin::ModelPlugins) if you need to apply - /// multiple plugins. - pub fn builder_with_plugins< + pub fn builder< Body, - HttpPl: ::aws_smithy_http_server::plugin::HttpMarker, - ModelPl: ::aws_smithy_http_server::plugin::ModelMarker, + L, + H: ::aws_smithy_http_server::plugin::HttpMarker, + M: ::aws_smithy_http_server::plugin::ModelMarker, >( - http_plugin: HttpPl, - model_plugin: ModelPl, - ) -> SimpleServiceBuilder { + config: Config, + ) -> SimpleServiceBuilder { SimpleServiceBuilder { + layer: config.layers, operation: None, - http_plugin, - model_plugin, + http_plugin: config.http_plugins, + model_plugin: config.model_plugins, } } - - /// Constructs a builder for [`SimpleService`]. - /// - /// Use [`SimpleService::builder_with_plugins`] if you need to specify plugins. - pub fn builder_without_plugins() -> SimpleServiceBuilder< - Body, - ::aws_smithy_http_server::plugin::IdentityPlugin, - ::aws_smithy_http_server::plugin::IdentityPlugin, - > { - Self::builder_with_plugins( - ::aws_smithy_http_server::plugin::IdentityPlugin, - ::aws_smithy_http_server::plugin::IdentityPlugin, - ) - } } impl SimpleService { @@ -325,55 +326,25 @@ impl SimpleService { ) -> ::aws_smithy_http_server::routing::IntoMakeServiceWithConnectInfo { ::aws_smithy_http_server::routing::IntoMakeServiceWithConnectInfo::new(self) } - - /// Applies a [`Layer`](::tower::Layer) uniformly to all routes. - pub fn layer(self, layer: &L) -> SimpleService - where - L: ::tower::Layer, - { - SimpleService { - router: self.router.map(|s| s.layer(layer)), - } - } - - /// Applies [`Route::new`](::aws_smithy_http_server::routing::Route::new) to all routes. - /// - /// This has the effect of erasing all types accumulated via [`layer`](SimpleService::layer). - pub fn boxed(self) -> SimpleService<::aws_smithy_http_server::routing::Route> - where - S: ::tower::Service< - ::http::Request, - Response = ::http::Response<::aws_smithy_http_server::body::BoxBody>, - Error = std::convert::Infallible, - >, - S: Clone + Send + 'static, - S::Future: Send + 'static, - { - self.layer(&::tower::layer::layer_fn( - ::aws_smithy_http_server::routing::Route::new, - )) - } } -impl ::tower::Service<::http::Request> for SimpleService +impl ::tower::Service for SimpleService where - S: ::tower::Service<::http::Request, Response = ::http::Response> + Clone, - RespB: ::http_body::Body + Send + 'static, - RespB::Error: Into>, + S: ::tower::Service, { - type Response = ::http::Response<::aws_smithy_http_server::body::BoxBody>; + type Response = S::Response; type Error = S::Error; - type Future = ::aws_smithy_http_server::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) } } @@ -395,13 +366,13 @@ impl Operation { } } } -impl ::aws_smithy_http_server::service::ContainsOperation - for SimpleService +impl ::aws_smithy_http_server::service::ContainsOperation + for SimpleService { const VALUE: Operation = Operation::Operation; } -impl ::aws_smithy_http_server::service::ServiceShape for SimpleService { +impl ::aws_smithy_http_server::service::ServiceShape for SimpleService { const ID: ::aws_smithy_http_server::shape_id::ShapeId = ::aws_smithy_http_server::shape_id::ShapeId::new( "com.amazonaws.simple#SimpleService", @@ -511,3 +482,80 @@ macro_rules! scope { scope! { @ $ name, True ($($ exclude)*) () (Operation) } }; } +#[derive(::std::fmt::Debug)] +pub struct Config { + layers: L, + http_plugins: H, + model_plugins: M, +} + +impl Config<(), (), ()> { + pub fn builder() -> config::Builder< + tower::layer::util::Identity, + ::aws_smithy_http_server::plugin::IdentityPlugin, + ::aws_smithy_http_server::plugin::IdentityPlugin, + > { + config::Builder { + layers: tower::layer::util::Identity::new(), + http_plugins: aws_smithy_http_server::plugin::IdentityPlugin, + model_plugins: aws_smithy_http_server::plugin::IdentityPlugin, + } + } +} + +/// Module hosting the builder for [`Config`]. +pub mod config { + use aws_smithy_http_server::plugin::{HttpMarker, ModelMarker, PluginStack}; + use tower::layer::util::Stack; + + #[derive(::std::fmt::Debug)] + pub struct Builder { + pub(crate) layers: L, + pub(crate) http_plugins: H, + pub(crate) model_plugins: M, + } + + impl Builder { + pub fn layer(self, layer: NewLayer) -> Builder, H, M> { + Builder { + layers: Stack::new(layer, self.layers), + http_plugins: self.http_plugins, + model_plugins: self.model_plugins, + } + } + + // 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, + ) -> Builder, M> { + Builder { + layers: self.layers, + http_plugins: PluginStack::new(http_plugin, self.http_plugins), + model_plugins: self.model_plugins, + } + } + + // 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, + ) -> Builder> { + Builder { + layers: self.layers, + http_plugins: self.http_plugins, + model_plugins: PluginStack::new(model_plugin, self.model_plugins), + } + } + + pub fn build(self) -> super::Config { + super::Config { + layers: self.layers, + http_plugins: self.http_plugins, + model_plugins: self.model_plugins, + } + } + } +} diff --git a/service/Cargo.toml b/service/Cargo.toml index bc4ec03..0bfc633 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -7,7 +7,9 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +aws-smithy-http-server = { path = "/home/ANT.AMAZON.COM/davidpz/code/smithy-rs-main/rust-runtime/aws-smithy-http-server" } hyper = { version = "0.14.25", features = ["server"] } +http = "0.2" tokio = "1.26.0" tracing = "0.1" # Generated using smithy-rs a78ac591fe4d969158bddabd4d6b1bf7a396cd5c diff --git a/service/src/main.rs b/service/src/main.rs index c208e36..fed9ecf 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -1,9 +1,18 @@ -use simple::SimpleService; +use aws_smithy_http_server::{ + instrumentation::InstrumentPlugin, layer::alb_health_check::AlbHealthCheckLayer, +}; +use simple::{Config, SimpleService}; use std::net::SocketAddr; #[tokio::main] async fn main() { - let app = SimpleService::builder_without_plugins() + let health_check_layer = + AlbHealthCheckLayer::from_handler("/ping", |_req| async { http::status::StatusCode::OK }); + let config = Config::builder() + .http_plugin(InstrumentPlugin) + .layer(&health_check_layer) + .build(); + let app = SimpleService::builder(config) .operation(operation) .build() .expect("failed to build an instance of SimpleService");