diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt index ce69131578b16377683e409f9249e55b1e82cd19..c4420fced545ec0786845853ebaa3982fa7a3022 100644 --- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt +++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGeneratorV2.kt @@ -332,6 +332,9 @@ class ServerServiceGeneratorV2( /// You must specify what plugins should be applied to the operations in this service. /// /// Use [`$serviceName::builder_without_plugins`] if you don't need to apply plugins. + /// + /// Check out [`PluginPipeline`](#{SmithyHttpServer}::plugin::PluginPipeline) if you need to apply + /// multiple plugins. pub fn builder_with_plugins
(plugin: Plugin) -> $builderName { $builderName { #{NotSetFields:W}, diff --git a/design/src/server/anatomy.md b/design/src/server/anatomy.md index 3f90aec96e40747f02baabc26a8366e2e94ee418..75b577d944947d404ee139c723ee82af0cc8d8ef 100644 --- a/design/src/server/anatomy.md +++ b/design/src/server/anatomy.md @@ -589,12 +589,9 @@ The central trait is [`Plugin`](https://github.com/awslabs/smithy-rs/blob/4c5cbc ```rust /// A mapping from one [`Operation`] to another. Used to modify the behavior of -/// [`Upgradable`](crate::operation::Upgradable) and therefore the resulting service builder, +/// [`Upgradable`](crate::operation::Upgradable) and therefore the resulting service builder. /// /// The generics `Protocol` and `Op` allow the behavior to be parameterized. -/// -/// Every service builder enjoys [`Pluggable`] and therefore can be provided with a [`Plugin`] using -/// [`Pluggable::apply`]. pub trait Plugin: Plugin
{
- /// Stacks another [`Plugin`], running them sequentially.
- fn stack for Pl where Pl: Plugin {}
diff --git a/rust-runtime/aws-smithy-http-server/src/plugin/pipeline.rs b/rust-runtime/aws-smithy-http-server/src/plugin/pipeline.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7b390fc903ad0907efa493a8874fb102e2d6d265
--- /dev/null
+++ b/rust-runtime/aws-smithy-http-server/src/plugin/pipeline.rs
@@ -0,0 +1,183 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+use crate::operation::Operation;
+use crate::plugin::{IdentityPlugin, Plugin, PluginStack};
+
+/// A wrapper struct for composing [`Plugin`]s.
+/// It is used as input for the `builder_with_plugins` method on the generate service struct
+/// (e.g. `PokemonService::builder_with_plugins`).
+///
+/// ## Applying plugins in a sequence
+///
+/// You can use the [`push`](PluginPipeline::push) method to apply a new plugin after the ones that
+/// have already been registered.
+///
+/// ```rust
+/// use aws_smithy_http_server::plugin::PluginPipeline;
+/// # 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);
+/// ```
+///
+/// The plugins' runtime logic is executed in registration order.
+/// In our example above, `LoggingPlugin` would run first, while `MetricsPlugin` is executed last.
+///
+/// ## Wrapping the current plugin pipeline
+///
+/// From time to time, you might have a need to transform the entire pipeline that has been built
+/// so far - e.g. you only want to apply those plugins for a specific operation.
+///
+/// `PluginPipeline` is itself a [`Plugin`]: you can apply any transformation that expects a
+/// [`Plugin`] to an entire pipeline. In this case, we want to use
+/// [`filter_by_operation_name`](crate::plugin::filter_by_operation_name) to limit the scope of
+/// the logging and metrics plugins to the `CheckHealth` operation:
+///
+/// ```rust
+/// use aws_smithy_http_server::plugin::{filter_by_operation_name, PluginPipeline};
+/// # use aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin;
+/// # use aws_smithy_http_server::plugin::IdentityPlugin as MetricsPlugin;
+/// # use aws_smithy_http_server::plugin::IdentityPlugin as AuthPlugin;
+/// # struct CheckHealth;
+/// # impl CheckHealth { const NAME: &'static str = "MyName"; }
+///
+/// // The logging and metrics plugins will only be applied to the `CheckHealth` operation.
+/// let operation_specific_pipeline = filter_by_operation_name(
+/// PluginPipeline::new()
+/// .push(LoggingPlugin)
+/// .push(MetricsPlugin),
+/// |name| name == CheckHealth::NAME
+/// );
+/// let pipeline = PluginPipeline::new()
+/// .push(operation_specific_pipeline)
+/// // The auth plugin will be applied to all operations
+/// .push(AuthPlugin);
+/// ```
+///
+/// ## Concatenating two plugin pipelines
+///
+/// `PluginPipeline` is a good way to bundle together multiple plugins, ensuring they are all
+/// registered in the correct order.
+///
+/// Since `PluginPipeline` is itself a [`Plugin`], you can use the [`push`](PluginPipeline::push) to
+/// append, at once, all the plugins in another pipeline to the current pipeline:
+///
+/// ```rust
+/// use aws_smithy_http_server::plugin::{IdentityPlugin, PluginPipeline, PluginStack};
+/// # use aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin;
+/// # use aws_smithy_http_server::plugin::IdentityPlugin as MetricsPlugin;
+/// # use aws_smithy_http_server::plugin::IdentityPlugin as AuthPlugin;
+///
+/// pub fn get_bundled_pipeline() -> PluginPipeline (P);
+
+impl Default for PluginPipeline PluginPipeline {
+ /// Apply a new plugin after the ones that have already been registered.
+ ///
+ /// ```rust
+ /// use aws_smithy_http_server::plugin::PluginPipeline;
+ /// # 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);
+ /// ```
+ ///
+ /// The plugins' runtime logic is executed in registration order.
+ /// In our example above, `LoggingPlugin` would run first, while `MetricsPlugin` is executed last.
+ ///
+ /// ## Implementation notes
+ ///
+ /// Plugins are applied to the underlying [`Operation`] in opposite order compared
+ /// to their registration order.
+ /// But most [`Plugin::map`] implementations desugar to appending a layer to [`Operation`],
+ /// usually via [`Operation::layer`].
+ /// As an example:
+ ///
+ /// ```rust,compile_fail
+ /// #[derive(Debug)]
+ /// pub struct PrintPlugin;
+ ///
+ /// impl Plugin for PrintPlugin
+ /// // [...]
+ /// {
+ /// // [...]
+ /// fn map(&self, input: Operation Plugin for PluginPipeline ,
+{
+ type Service = InnerPlugin::Service;
+ type Layer = InnerPlugin::Layer;
+
+ fn map(&self, input: Operation) -> Operation) -> Operation