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 191 192 193 194 195 196 197 198 199 200 201
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
// If you make any updates to this file (including Rust docs), make sure you make them to
// `model_plugins.rs` too!
use crate::plugin::{IdentityPlugin, Plugin, PluginStack};
use super::{HttpMarker, LayerPlugin};
/// A wrapper struct for composing HTTP plugins.
///
/// ## Applying plugins in a sequence
///
/// You can use the [`push`](HttpPlugins::push) method to apply a new HTTP plugin after the ones that
/// have already been registered.
///
/// ```rust
/// 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 http_plugins = HttpPlugins::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.
///
/// `HttpPlugins` is itself a [`Plugin`]: you can apply any transformation that expects a
/// [`Plugin`] to an entire pipeline. In this case, we could use a [scoped
/// plugin](crate::plugin::Scoped) to limit the scope of the logging and metrics plugins to the
/// `CheckHealth` operation:
///
/// ```rust
/// use aws_smithy_http_server::scope;
/// use aws_smithy_http_server::plugin::{HttpPlugins, Scoped};
/// # 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;
/// use aws_smithy_http_server::shape_id::ShapeId;
/// # #[derive(PartialEq)]
/// # enum Operation { CheckHealth }
/// # struct CheckHealth;
/// # impl CheckHealth { const ID: ShapeId = ShapeId::new("namespace#MyName", "namespace", "MyName"); }
///
/// // The logging and metrics plugins will only be applied to the `CheckHealth` operation.
/// let plugin = HttpPlugins::new()
/// .push(LoggingPlugin)
/// .push(MetricsPlugin);
///
/// scope! {
/// struct OnlyCheckHealth {
/// includes: [CheckHealth],
/// excludes: [/* The rest of the operations go here */]
/// }
/// }
///
/// let filtered_plugin = Scoped::new::<OnlyCheckHealth>(&plugin);
/// let http_plugins = HttpPlugins::new()
/// .push(filtered_plugin)
/// // The auth plugin will be applied to all operations.
/// .push(AuthPlugin);
/// ```
///
/// ## Concatenating two collections of HTTP plugins
///
/// `HttpPlugins` is a good way to bundle together multiple plugins, ensuring they are all
/// registered in the correct order.
///
/// Since `HttpPlugins` is itself a HTTP plugin (it implements the `HttpMarker` trait), you can use
/// the [`push`](HttpPlugins::push) to append, at once, all the HTTP plugins in another
/// `HttpPlugins` to the current `HttpPlugins`:
///
/// ```rust
/// use aws_smithy_http_server::plugin::{IdentityPlugin, HttpPlugins, 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_http_plugins() -> HttpPlugins<PluginStack<MetricsPlugin, PluginStack<LoggingPlugin, IdentityPlugin>>> {
/// HttpPlugins::new().push(LoggingPlugin).push(MetricsPlugin)
/// }
///
/// let http_plugins = HttpPlugins::new()
/// .push(AuthPlugin)
/// .push(get_bundled_http_plugins());
/// ```
///
/// ## Providing custom methods on `HttpPlugins`
///
/// You use an **extension trait** to add custom methods on `HttpPlugins`.
///
/// This is a simple example using `AuthPlugin`:
///
/// ```rust
/// 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) -> HttpPlugins<PluginStack<AuthPlugin, CurrentPlugins>>;
/// }
///
/// impl<CurrentPlugins> AuthPluginExt<CurrentPlugins> for HttpPlugins<CurrentPlugins> {
/// fn with_auth(self) -> HttpPlugins<PluginStack<AuthPlugin, CurrentPlugins>> {
/// self.push(AuthPlugin)
/// }
/// }
///
/// let http_plugins = HttpPlugins::new()
/// .push(LoggingPlugin)
/// // Our custom method!
/// .with_auth();
/// ```
#[derive(Debug)]
pub struct HttpPlugins<P>(pub(crate) P);
impl Default for HttpPlugins<IdentityPlugin> {
fn default() -> Self {
Self(IdentityPlugin)
}
}
impl HttpPlugins<IdentityPlugin> {
/// Create an empty [`HttpPlugins`].
///
/// You can use [`HttpPlugins::push`] to add plugins to it.
pub fn new() -> Self {
Self::default()
}
}
impl<P> HttpPlugins<P> {
/// Apply a new HTTP plugin after the ones that have already been registered.
///
/// ```rust
/// 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 http_plugins = HttpPlugins::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 [`Service`](tower::Service) in opposite order compared
/// to their registration order.
///
/// As an example:
///
/// ```rust,compile_fail
/// #[derive(Debug)]
/// pub struct PrintPlugin;
///
/// impl<Ser, Op, S> Plugin<Ser, Op, T> for PrintPlugin
/// // [...]
/// {
/// // [...]
/// fn apply(&self, inner: T) -> Self::Service {
/// PrintService {
/// inner,
/// service_id: Ser::ID,
/// operation_id: Op::ID
/// }
/// }
/// }
/// ```
// We eagerly require `NewPlugin: HttpMarker`, despite not really needing it, because compiler
// errors get _substantially_ better if the user makes a mistake.
pub fn push<NewPlugin: HttpMarker>(self, new_plugin: NewPlugin) -> HttpPlugins<PluginStack<NewPlugin, P>> {
HttpPlugins(PluginStack::new(new_plugin, self.0))
}
/// Applies a single [`tower::Layer`] to all operations _before_ they are deserialized.
pub fn layer<L>(self, layer: L) -> HttpPlugins<PluginStack<LayerPlugin<L>, P>> {
HttpPlugins(PluginStack::new(LayerPlugin(layer), self.0))
}
}
impl<Ser, Op, T, InnerPlugin> Plugin<Ser, Op, T> for HttpPlugins<InnerPlugin>
where
InnerPlugin: Plugin<Ser, Op, T>,
{
type Output = InnerPlugin::Output;
fn apply(&self, input: T) -> Self::Output {
self.0.apply(input)
}
}
impl<InnerPlugin> HttpMarker for HttpPlugins<InnerPlugin> where InnerPlugin: HttpMarker {}