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

Better distinguish model and HTTP plugins #2827

Merged
merged 6 commits into from
Jul 4, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions CHANGELOG.next.toml
Original file line number Diff line number Diff line change
Expand Up @@ -586,3 +586,9 @@ message = "The naming `make_token` for fields and the API of `IdempotencyTokenPr
references = ["smithy-rs#2783"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "ysaito1001"

[[smithy-rs]]
message = "Server `Plugin`s now need to be marked as a `HttpPlugin`, a `ModelPlugin`, or both. Check out the `plugin` module documentation for more information."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've got a huge changelog entry about the type refactors above - perhaps it'd be nice to squash this one into that and uphold its narrative?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in a58ec48.

references = ["smithy-rs#2827"]
meta = { "breaking" = true, "tada" = false, "bug" = false, "target" = "server" }
author = "david-perez"
Original file line number Diff line number Diff line change
Expand Up @@ -106,21 +106,22 @@ open class ServerRootGenerator(
//! #### Plugins
//!
//! The [`$serviceName::builder_with_plugins`] method, returning [`$builderName`],
//! accepts a [`Plugin`](aws_smithy_http_server::plugin::Plugin).
//! accepts a [`HttpPlugin`](aws_smithy_http_server::plugin::HttpPlugin) and a
//! [`ModelPlugin`](aws_smithy_http_server::plugin::HttpPlugin).
//! Plugins allow you to build middleware which is aware of the operation it is being applied to.
//!
//! ```rust
//! ## use #{SmithyHttpServer}::plugin::IdentityPlugin;
//! ## use #{SmithyHttpServer}::plugin::IdentityPlugin as LoggingPlugin;
//! ## use #{SmithyHttpServer}::plugin::IdentityPlugin as MetricsPlugin;
//! ## use #{Hyper}::Body;
//! use #{SmithyHttpServer}::plugin::PluginPipeline;
//! use #{SmithyHttpServer}::plugin::HttpPlugins;
//! use $crateName::{$serviceName, $builderName};
//!
//! let plugins = PluginPipeline::new()
//! let http_plugins = HttpPlugins::new()
//! .push(LoggingPlugin)
//! .push(MetricsPlugin);
//! let builder: $builderName<Body, _, _> = $serviceName::builder_with_plugins(plugins, IdentityPlugin);
//! let builder: $builderName<Body, _, _> = $serviceName::builder_with_plugins(http_plugins, IdentityPlugin);
//! ```
//!
//! Check out [`#{SmithyHttpServer}::plugin`] to learn more about plugins.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ 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.CodegenContext
import software.amazon.smithy.rust.codegen.server.smithy.ServerCargoDependency
import software.amazon.smithy.rust.codegen.server.smithy.ServerRuntimeType

class ServerRuntimeTypesReExportsGenerator(
codegenContext: CodegenContext,
) {
private val runtimeConfig = codegenContext.runtimeConfig
private val codegenScope = arrayOf(
"Router" to ServerRuntimeType.router(runtimeConfig),
"SmithyHttpServer" to ServerCargoDependency.smithyHttpServer(runtimeConfig).toType(),
)

Expand All @@ -30,8 +28,11 @@ class ServerRuntimeTypesReExportsGenerator(
pub use #{SmithyHttpServer}::operation::OperationShape;
}
pub mod plugin {
pub use #{SmithyHttpServer}::plugin::HttpPlugins;
pub use #{SmithyHttpServer}::plugin::ModelPlugins;
pub use #{SmithyHttpServer}::plugin::HttpPlugin;
pub use #{SmithyHttpServer}::plugin::ModelPlugin;
pub use #{SmithyHttpServer}::plugin::Plugin;
pub use #{SmithyHttpServer}::plugin::PluginPipeline;
pub use #{SmithyHttpServer}::plugin::PluginStack;
}
pub mod request {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,31 +136,31 @@ class ServerServiceGenerator(
where
HandlerType: #{SmithyHttpServer}::operation::Handler<crate::operation_shape::$structName, HandlerExtractors>,

ModelPlugin: #{SmithyHttpServer}::plugin::Plugin<
ModelPl: #{SmithyHttpServer}::plugin::Plugin<
$serviceName,
crate::operation_shape::$structName,
#{SmithyHttpServer}::operation::IntoService<crate::operation_shape::$structName, HandlerType>
>,
#{SmithyHttpServer}::operation::UpgradePlugin::<UpgradeExtractors>: #{SmithyHttpServer}::plugin::Plugin<
$serviceName,
crate::operation_shape::$structName,
ModelPlugin::Output
ModelPl::Output
>,
HttpPlugin: #{SmithyHttpServer}::plugin::Plugin<
HttpPl: #{SmithyHttpServer}::plugin::Plugin<
$serviceName,
crate::operation_shape::$structName,
<
#{SmithyHttpServer}::operation::UpgradePlugin::<UpgradeExtractors>
as #{SmithyHttpServer}::plugin::Plugin<
$serviceName,
crate::operation_shape::$structName,
ModelPlugin::Output
ModelPl::Output
>
>::Output
>,

HttpPlugin::Output: #{Tower}::Service<#{Http}::Request<Body>, Response = #{Http}::Response<#{SmithyHttpServer}::body::BoxBody>, Error = ::std::convert::Infallible> + Clone + Send + 'static,
<HttpPlugin::Output as #{Tower}::Service<#{Http}::Request<Body>>>::Future: Send + 'static,
HttpPl::Output: #{Tower}::Service<#{Http}::Request<Body>, Response = #{Http}::Response<#{SmithyHttpServer}::body::BoxBody>, Error = ::std::convert::Infallible> + Clone + Send + 'static,
<HttpPl::Output as #{Tower}::Service<#{Http}::Request<Body>>>::Future: Send + 'static,

{
use #{SmithyHttpServer}::operation::OperationShapeExt;
Expand Down Expand Up @@ -199,31 +199,31 @@ class ServerServiceGenerator(
where
S: #{SmithyHttpServer}::operation::OperationService<crate::operation_shape::$structName, ServiceExtractors>,

ModelPlugin: #{SmithyHttpServer}::plugin::Plugin<
ModelPl: #{SmithyHttpServer}::plugin::Plugin<
$serviceName,
crate::operation_shape::$structName,
#{SmithyHttpServer}::operation::Normalize<crate::operation_shape::$structName, S>
>,
#{SmithyHttpServer}::operation::UpgradePlugin::<UpgradeExtractors>: #{SmithyHttpServer}::plugin::Plugin<
$serviceName,
crate::operation_shape::$structName,
ModelPlugin::Output
ModelPl::Output
>,
HttpPlugin: #{SmithyHttpServer}::plugin::Plugin<
HttpPl: #{SmithyHttpServer}::plugin::Plugin<
$serviceName,
crate::operation_shape::$structName,
<
#{SmithyHttpServer}::operation::UpgradePlugin::<UpgradeExtractors>
as #{SmithyHttpServer}::plugin::Plugin<
$serviceName,
crate::operation_shape::$structName,
ModelPlugin::Output
ModelPl::Output
>
>::Output
>,

HttpPlugin::Output: #{Tower}::Service<#{Http}::Request<Body>, Response = #{Http}::Response<#{SmithyHttpServer}::body::BoxBody>, Error = ::std::convert::Infallible> + Clone + Send + 'static,
<HttpPlugin::Output as #{Tower}::Service<#{Http}::Request<Body>>>::Future: Send + 'static,
HttpPl::Output: #{Tower}::Service<#{Http}::Request<Body>, Response = #{Http}::Response<#{SmithyHttpServer}::body::BoxBody>, Error = ::std::convert::Infallible> + Clone + Send + 'static,
<HttpPl::Output as #{Tower}::Service<#{Http}::Request<Body>>>::Future: Send + 'static,

{
use #{SmithyHttpServer}::operation::OperationShapeExt;
Expand Down Expand Up @@ -394,16 +394,16 @@ class ServerServiceGenerator(

/** Returns a `Writable` containing the builder struct definition and its implementations. */
private fun builder(): Writable = writable {
val builderGenerics = listOf(builderBodyGenericTypeName, "HttpPlugin", "ModelPlugin").joinToString(", ")
val builderGenerics = listOf(builderBodyGenericTypeName, "HttpPl", "ModelPl").joinToString(", ")
rustTemplate(
"""
/// The service builder for [`$serviceName`].
///
/// Constructed via [`$serviceName::builder_with_plugins`] or [`$serviceName::builder_without_plugins`].
pub struct $builderName<$builderGenerics> {
${builderFields.joinToString(", ")},
http_plugin: HttpPlugin,
model_plugin: ModelPlugin
http_plugin: HttpPl,
model_plugin: ModelPl
}

impl<$builderGenerics> $builderName<$builderGenerics> {
Expand Down Expand Up @@ -473,9 +473,10 @@ class ServerServiceGenerator(
///
/// Use [`$serviceName::builder_without_plugins`] if you don't need to apply plugins.
///
/// Check out [`PluginPipeline`](#{SmithyHttpServer}::plugin::PluginPipeline) if you need to apply
/// Check out [`HttpPlugins`](#{SmithyHttpServer}::plugin::HttpPlugins) and
/// [`ModelPlugins`](#{SmithyHttpServer}::plugin::ModelPlugins) if you need to apply
/// multiple plugins.
pub fn builder_with_plugins<Body, HttpPlugin, ModelPlugin>(http_plugin: HttpPlugin, model_plugin: ModelPlugin) -> $builderName<Body, HttpPlugin, ModelPlugin> {
pub fn builder_with_plugins<Body, HttpPl: #{SmithyHttpServer}::plugin::HttpPlugin, ModelPl: #{SmithyHttpServer}::plugin::ModelPlugin>(http_plugin: HttpPl, model_plugin: ModelPl) -> $builderName<Body, HttpPl, ModelPl> {
$builderName {
#{NotSetFields:W},
http_plugin,
Expand Down
18 changes: 9 additions & 9 deletions design/src/server/anatomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,40 +466,40 @@ stateDiagram-v2
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.

You might find yourself wanting to apply _multiple_ plugins to your service.
This can be accommodated via [`PluginPipeline`].
This can be accommodated via [`HttpPlugins`] and [`ModelPlugins`].

```rust
# extern crate aws_smithy_http_server;
use aws_smithy_http_server::plugin::PluginPipeline;
use aws_smithy_http_server::plugin::HttpPlugins;
# use aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin;
# use aws_smithy_http_server::plugin::IdentityPlugin as MetricsPlugin;

let pipeline = PluginPipeline::new().push(LoggingPlugin).push(MetricsPlugin);
let http_plugins = HttpPlugins::new().push(LoggingPlugin).push(MetricsPlugin);
```

The plugins' runtime logic is executed in registration order.
In the example above, `LoggingPlugin` would run first, while `MetricsPlugin` is executed last.

If you are vending a plugin, you can leverage `PluginPipeline` as an extension point: you can add custom methods to it using an extension trait.
If you are vending a plugin, you can leverage `HttpPlugins` or `ModelPlugins` as an extension point: you can add custom methods to it using an extension trait.
For example:

```rust
# extern crate aws_smithy_http_server;
use aws_smithy_http_server::plugin::{PluginPipeline, PluginStack};
use aws_smithy_http_server::plugin::{HttpPlugins, PluginStack};
# use aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin;
# use aws_smithy_http_server::plugin::IdentityPlugin as AuthPlugin;

pub trait AuthPluginExt<CurrentPlugins> {
fn with_auth(self) -> PluginPipeline<PluginStack<AuthPlugin, CurrentPlugins>>;
fn with_auth(self) -> HttpPlugins<PluginStack<AuthPlugin, CurrentPlugins>>;
}

impl<CurrentPlugins> AuthPluginExt<CurrentPlugins> for PluginPipeline<CurrentPlugins> {
fn with_auth(self) -> PluginPipeline<PluginStack<AuthPlugin, CurrentPlugins>> {
impl<CurrentPlugins> AuthPluginExt<CurrentPlugins> for HttpPlugins<CurrentPlugins> {
fn with_auth(self) -> HttpPlugins<PluginStack<AuthPlugin, CurrentPlugins>> {
self.push(AuthPlugin)
}
}

let pipeline = PluginPipeline::new()
let http_plugins = HttpPlugins::new()
.push(LoggingPlugin)
// Our custom method!
.with_auth();
Expand Down
4 changes: 2 additions & 2 deletions design/src/server/instrumentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ This is enabled via the `instrument` method provided by the `aws_smithy_http_ser
# let handler = |req: GetPokemonSpeciesInput| async { Result::<GetPokemonSpeciesOutput, GetPokemonSpeciesError>::Ok(todo!()) };
use aws_smithy_http_server::{
instrumentation::InstrumentExt,
plugin::{IdentityPlugin, PluginPipeline}
plugin::{IdentityPlugin, HttpPlugins}
};
use pokemon_service_server_sdk::PokemonService;

let http_plugins = PluginPipeline::new().instrument();
let http_plugins = HttpPlugins::new().instrument();
let app = PokemonService::builder_with_plugins(http_plugins, IdentityPlugin)
.get_pokemon_species(handler)
/* ... */
Expand Down
26 changes: 13 additions & 13 deletions design/src/server/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ scope! {
// Construct `LoggingLayer`.
let logging_plugin = LayerPlugin(LoggingLayer::new());
let logging_plugin = Scoped::new::<LoggingScope>(logging_plugin);
let http_plugins = PluginPipeline::new().push(logging_plugin);
let http_plugins = HttpPlugins::new().push(logging_plugin);

let app /* : PokemonService<Route<B>> */ = PokemonService::builder_with_plugins(http_plugins, IdentityPlugin)
.get_pokemon_species(handler)
Expand Down Expand Up @@ -265,7 +265,7 @@ scope! {
// Construct `BufferLayer`.
let buffer_plugin = LayerPlugin(BufferLayer::new(3));
let buffer_plugin = Scoped::new::<BufferScope>(buffer_plugin);
let model_plugins = PluginPipeline::new().push(buffer_plugin);
let model_plugins = ModelPlugins::new().push(buffer_plugin);

let app /* : PokemonService<Route<B>> */ = PokemonService::builder_with_plugins(IdentityPlugin, model_plugins)
.get_pokemon_species(handler)
Expand Down Expand Up @@ -347,23 +347,23 @@ where
}
```

You can provide a custom method to add your plugin to a `PluginPipeline` via an extension trait:
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
# extern crate aws_smithy_http_server;
# pub struct PrintPlugin;
use aws_smithy_http_server::plugin::{PluginPipeline, PluginStack};
use aws_smithy_http_server::plugin::{HttpPlugins, PluginStack};

/// This provides a [`print`](PrintExt::print) method on [`PluginPipeline`].
/// This provides a [`print`](PrintExt::print) method on [`HttpPlugins`].
pub trait PrintExt<ExistingPlugins> {
/// Causes all operations to print the operation name when called.
///
/// This works by applying the [`PrintPlugin`].
fn print(self) -> PluginPipeline<PluginStack<PrintPlugin, ExistingPlugins>>;
fn print(self) -> HttpPlugins<PluginStack<PrintPlugin, ExistingPlugins>>;
}

impl<ExistingPlugins> PrintExt<ExistingPlugins> for PluginPipeline<ExistingPlugins> {
fn print(self) -> PluginPipeline<PluginStack<PrintPlugin, ExistingPlugins>> {
impl<ExistingPlugins> PrintExt<ExistingPlugins> for HttpPlugins<ExistingPlugins> {
fn print(self) -> HttpPlugins<PluginStack<PrintPlugin, ExistingPlugins>> {
self.push(PrintPlugin)
}
}
Expand All @@ -377,14 +377,14 @@ This allows for:
# use aws_smithy_http_server::plugin::{PluginStack, Plugin};
# struct PrintPlugin;
# impl<Ser, Op, T> Plugin<Ser, Op, T> for PrintPlugin { type Output = T; fn apply(&self, svc: T) -> Self::Output { svc }}
# trait PrintExt<EP> { fn print(self) -> PluginPipeline<PluginStack<PrintPlugin, EP>>; }
# impl<EP> PrintExt<EP> for PluginPipeline<EP> { fn print(self) -> PluginPipeline<PluginStack<PrintPlugin, EP>> { self.push(PrintPlugin) }}
# trait PrintExt<EP> { fn print(self) -> HttpPlugins<PluginStack<PrintPlugin, EP>>; }
# impl<EP> PrintExt<EP> for HttpPlugins<EP> { fn print(self) -> HttpPlugins<PluginStack<PrintPlugin, EP>> { self.push(PrintPlugin) }}
# use pokemon_service_server_sdk::{operation_shape::GetPokemonSpecies, input::*, output::*, error::*};
# let handler = |req: GetPokemonSpeciesInput| async { Result::<GetPokemonSpeciesOutput, GetPokemonSpeciesError>::Ok(todo!()) };
use aws_smithy_http_server::plugin::{IdentityPlugin, PluginPipeline};
use aws_smithy_http_server::plugin::{IdentityPlugin, HttpPlugins};
use pokemon_service_server_sdk::PokemonService;

let http_plugins = PluginPipeline::new()
let http_plugins = HttpPlugins::new()
// [..other plugins..]
// The custom method!
.print();
Expand All @@ -397,4 +397,4 @@ let app /* : PokemonService<Route<B>> */ = PokemonService::builder_with_plugins(
```

The custom `print` method hides the details of the `Plugin` trait from the average consumer.
They interact with the utility methods on `PluginPipeline` and enjoy the self-contained documentation.
They interact with the utility methods on `HttpPlugins` and enjoy the self-contained documentation.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::{
};

use aws_smithy_http::body::SdkBody;
use aws_smithy_http_server::plugin::{IdentityPlugin, Plugin, PluginPipeline};
use aws_smithy_http_server::plugin::{HttpPlugin, HttpPlugins, IdentityPlugin, Plugin};
use tower::{Layer, Service};

use pokemon_service_client::{operation::do_nothing::DoNothingInput, Config};
Expand All @@ -34,13 +34,15 @@ async fn plugin_layers_are_executed_in_registration_order() {
// We can then check the vector content to verify the invocation order
let output = Arc::new(Mutex::new(Vec::new()));

let pipeline = PluginPipeline::new()
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(pipeline, IdentityPlugin)
.do_nothing(do_nothing)
.build_unchecked();
let mut app = pokemon_service_server_sdk::PokemonService::builder_with_plugins(
http_plugins,
IdentityPlugin,
)
.do_nothing(do_nothing)
.build_unchecked();
let request = DoNothingInput::builder()
.build()
.unwrap()
Expand Down Expand Up @@ -77,6 +79,8 @@ impl<Ser, Op, T> Plugin<Ser, Op, T> for SentinelPlugin {
}
}

impl HttpPlugin for SentinelPlugin {}

/// A [`Service`] that adds a print log.
#[derive(Clone, Debug)]
pub struct SentinelService<S> {
Expand Down
6 changes: 3 additions & 3 deletions examples/pokemon-service/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use std::{net::SocketAddr, sync::Arc};
use aws_smithy_http_server::{
extension::OperationExtensionExt,
instrumentation::InstrumentExt,
plugin::{alb_health_check::AlbHealthCheckLayer, IdentityPlugin, PluginPipeline, Scoped},
plugin::{alb_health_check::AlbHealthCheckLayer, HttpPlugins, IdentityPlugin, Scoped},
request::request_id::ServerRequestIdProviderLayer,
AddExtensionLayer,
};
Expand Down Expand Up @@ -51,9 +51,9 @@ pub async fn main() {
}
}
// Scope the `PrintPlugin`, defined in `plugin.rs`, to `PrintScope`
let print_plugin = Scoped::new::<PrintScope>(PluginPipeline::new().print());
let print_plugin = Scoped::new::<PrintScope>(HttpPlugins::new().print());

let plugins = PluginPipeline::new()
let plugins = HttpPlugins::new()
// Apply the scoped `PrintPlugin`
.push(print_plugin)
// Apply the `OperationExtensionPlugin` defined in `aws_smithy_http_server::extension`. This allows other
Expand Down
Loading
Loading