1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
/*
* 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};
use super::HttpLayer;
/// 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<PluginStack<MetricsPlugin, PluginStack<LoggingPlugin, IdentityPlugin>>> {
/// PluginPipeline::new().push(LoggingPlugin).push(MetricsPlugin)
/// }
///
/// let pipeline = PluginPipeline::new()
/// .push(AuthPlugin)
/// .push(get_bundled_pipeline());
/// ```
///
/// ## Providing custom methods on `PluginPipeline`
///
/// You use an **extension trait** to add custom methods on `PluginPipeline`.
///
/// This is a simple example using `AuthPlugin`:
///
/// ```rust
/// use aws_smithy_http_server::plugin::{PluginPipeline, 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>>;
/// }
///
/// impl<CurrentPlugins> AuthPluginExt<CurrentPlugins> for PluginPipeline<CurrentPlugins> {
/// fn with_auth(self) -> PluginPipeline<PluginStack<AuthPlugin, CurrentPlugins>> {
/// self.push(AuthPlugin)
/// }
/// }
///
/// let pipeline = PluginPipeline::new()
/// .push(LoggingPlugin)
/// // Our custom method!
/// .with_auth();
/// ```
pub struct PluginPipeline<P>(P);
impl Default for PluginPipeline<IdentityPlugin> {
fn default() -> Self {
Self(IdentityPlugin)
}
}
impl PluginPipeline<IdentityPlugin> {
/// Create an empty [`PluginPipeline`].
///
/// You can use [`PluginPipeline::push`] to add plugins to it.
pub fn new() -> Self {
Self::default()
}
}
impl<P> PluginPipeline<P> {
/// 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<P, Op, S, L> Plugin<P, Op, S, L> for PrintPlugin
/// // [...]
/// {
/// // [...]
/// fn map(&self, input: Operation<S, L>) -> Operation<Self::Service, Self::Layer> {
/// input.layer(PrintLayer { name: Op::NAME })
/// }
/// }
/// ```
///
/// The layer that is registered **last** via [`Operation::layer`] is the one that gets executed
/// **first** at runtime when a new request comes in, since it _wraps_ the underlying service.
///
/// This is why plugins in [`PluginPipeline`] are applied in opposite order compared to their
/// registration order: this ensures that, _at runtime_, their logic is executed
/// in registration order.
pub fn push<NewPlugin>(self, new_plugin: NewPlugin) -> PluginPipeline<PluginStack<NewPlugin, P>> {
PluginPipeline(PluginStack::new(new_plugin, self.0))
}
/// Applies a single [`tower::Layer`] to all operations _before_ they are deserialized.
pub fn http_layer<L>(self, layer: L) -> PluginPipeline<PluginStack<HttpLayer<L>, P>> {
PluginPipeline(PluginStack::new(HttpLayer(layer), self.0))
}
}
impl<P, Op, S, L, InnerPlugin> Plugin<P, Op, S, L> for PluginPipeline<InnerPlugin>
where
InnerPlugin: Plugin<P, Op, S, L>,
{
type Service = InnerPlugin::Service;
type Layer = InnerPlugin::Layer;
fn map(&self, input: Operation<S, L>) -> Operation<Self::Service, Self::Layer> {
self.0.map(input)
}
}