aws_smithy_http_server/plugin/http_plugins.rs
1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6// If you make any updates to this file (including Rust docs), make sure you make them to
7// `model_plugins.rs` too!
8
9use crate::plugin::{IdentityPlugin, Plugin, PluginStack};
10
11use super::{HttpMarker, LayerPlugin};
12
13/// A wrapper struct for composing HTTP plugins.
14///
15/// ## Applying plugins in a sequence
16///
17/// You can use the [`push`](HttpPlugins::push) method to apply a new HTTP plugin after the ones that
18/// have already been registered.
19///
20/// ```rust
21/// use aws_smithy_http_server::plugin::HttpPlugins;
22/// # use aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin;
23/// # use aws_smithy_http_server::plugin::IdentityPlugin as MetricsPlugin;
24///
25/// let http_plugins = HttpPlugins::new().push(LoggingPlugin).push(MetricsPlugin);
26/// ```
27///
28/// The plugins' runtime logic is executed in registration order.
29/// In our example above, `LoggingPlugin` would run first, while `MetricsPlugin` is executed last.
30///
31/// ## Wrapping the current plugin pipeline
32///
33/// From time to time, you might have a need to transform the entire pipeline that has been built
34/// so far - e.g. you only want to apply those plugins for a specific operation.
35///
36/// `HttpPlugins` is itself a [`Plugin`]: you can apply any transformation that expects a
37/// [`Plugin`] to an entire pipeline. In this case, we could use a [scoped
38/// plugin](crate::plugin::Scoped) to limit the scope of the logging and metrics plugins to the
39/// `CheckHealth` operation:
40///
41/// ```rust
42/// use aws_smithy_http_server::scope;
43/// use aws_smithy_http_server::plugin::{HttpPlugins, Scoped};
44/// # use aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin;
45/// # use aws_smithy_http_server::plugin::IdentityPlugin as MetricsPlugin;
46/// # use aws_smithy_http_server::plugin::IdentityPlugin as AuthPlugin;
47/// use aws_smithy_http_server::shape_id::ShapeId;
48/// # #[derive(PartialEq)]
49/// # enum Operation { CheckHealth }
50/// # struct CheckHealth;
51/// # impl CheckHealth { const ID: ShapeId = ShapeId::new("namespace#MyName", "namespace", "MyName"); }
52///
53/// // The logging and metrics plugins will only be applied to the `CheckHealth` operation.
54/// let plugin = HttpPlugins::new()
55/// .push(LoggingPlugin)
56/// .push(MetricsPlugin);
57///
58/// scope! {
59/// struct OnlyCheckHealth {
60/// includes: [CheckHealth],
61/// excludes: [/* The rest of the operations go here */]
62/// }
63/// }
64///
65/// let filtered_plugin = Scoped::new::<OnlyCheckHealth>(&plugin);
66/// let http_plugins = HttpPlugins::new()
67/// .push(filtered_plugin)
68/// // The auth plugin will be applied to all operations.
69/// .push(AuthPlugin);
70/// ```
71///
72/// ## Concatenating two collections of HTTP plugins
73///
74/// `HttpPlugins` is a good way to bundle together multiple plugins, ensuring they are all
75/// registered in the correct order.
76///
77/// Since `HttpPlugins` is itself a HTTP plugin (it implements the `HttpMarker` trait), you can use
78/// the [`push`](HttpPlugins::push) to append, at once, all the HTTP plugins in another
79/// `HttpPlugins` to the current `HttpPlugins`:
80///
81/// ```rust
82/// use aws_smithy_http_server::plugin::{IdentityPlugin, HttpPlugins, PluginStack};
83/// # use aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin;
84/// # use aws_smithy_http_server::plugin::IdentityPlugin as MetricsPlugin;
85/// # use aws_smithy_http_server::plugin::IdentityPlugin as AuthPlugin;
86///
87/// pub fn get_bundled_http_plugins() -> HttpPlugins<PluginStack<MetricsPlugin, PluginStack<LoggingPlugin, IdentityPlugin>>> {
88/// HttpPlugins::new().push(LoggingPlugin).push(MetricsPlugin)
89/// }
90///
91/// let http_plugins = HttpPlugins::new()
92/// .push(AuthPlugin)
93/// .push(get_bundled_http_plugins());
94/// ```
95///
96/// ## Providing custom methods on `HttpPlugins`
97///
98/// You use an **extension trait** to add custom methods on `HttpPlugins`.
99///
100/// This is a simple example using `AuthPlugin`:
101///
102/// ```rust
103/// use aws_smithy_http_server::plugin::{HttpPlugins, PluginStack};
104/// # use aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin;
105/// # use aws_smithy_http_server::plugin::IdentityPlugin as AuthPlugin;
106///
107/// pub trait AuthPluginExt<CurrentPlugins> {
108/// fn with_auth(self) -> HttpPlugins<PluginStack<AuthPlugin, CurrentPlugins>>;
109/// }
110///
111/// impl<CurrentPlugins> AuthPluginExt<CurrentPlugins> for HttpPlugins<CurrentPlugins> {
112/// fn with_auth(self) -> HttpPlugins<PluginStack<AuthPlugin, CurrentPlugins>> {
113/// self.push(AuthPlugin)
114/// }
115/// }
116///
117/// let http_plugins = HttpPlugins::new()
118/// .push(LoggingPlugin)
119/// // Our custom method!
120/// .with_auth();
121/// ```
122#[derive(Debug)]
123pub struct HttpPlugins<P>(pub(crate) P);
124
125impl Default for HttpPlugins<IdentityPlugin> {
126 fn default() -> Self {
127 Self(IdentityPlugin)
128 }
129}
130
131impl HttpPlugins<IdentityPlugin> {
132 /// Create an empty [`HttpPlugins`].
133 ///
134 /// You can use [`HttpPlugins::push`] to add plugins to it.
135 pub fn new() -> Self {
136 Self::default()
137 }
138}
139
140impl<P> HttpPlugins<P> {
141 /// Apply a new HTTP plugin after the ones that have already been registered.
142 ///
143 /// ```rust
144 /// use aws_smithy_http_server::plugin::HttpPlugins;
145 /// # use aws_smithy_http_server::plugin::IdentityPlugin as LoggingPlugin;
146 /// # use aws_smithy_http_server::plugin::IdentityPlugin as MetricsPlugin;
147 ///
148 /// let http_plugins = HttpPlugins::new().push(LoggingPlugin).push(MetricsPlugin);
149 /// ```
150 ///
151 /// The plugins' runtime logic is executed in registration order.
152 /// In our example above, `LoggingPlugin` would run first, while `MetricsPlugin` is executed last.
153 ///
154 /// ## Implementation notes
155 ///
156 /// Plugins are applied to the underlying [`Service`](tower::Service) in opposite order compared
157 /// to their registration order.
158 ///
159 /// As an example:
160 ///
161 /// ```rust,compile_fail
162 /// #[derive(Debug)]
163 /// pub struct PrintPlugin;
164 ///
165 /// impl<Ser, Op, S> Plugin<Ser, Op, T> for PrintPlugin
166 /// // [...]
167 /// {
168 /// // [...]
169 /// fn apply(&self, inner: T) -> Self::Service {
170 /// PrintService {
171 /// inner,
172 /// service_id: Ser::ID,
173 /// operation_id: Op::ID
174 /// }
175 /// }
176 /// }
177 /// ```
178 // We eagerly require `NewPlugin: HttpMarker`, despite not really needing it, because compiler
179 // errors get _substantially_ better if the user makes a mistake.
180 pub fn push<NewPlugin: HttpMarker>(self, new_plugin: NewPlugin) -> HttpPlugins<PluginStack<NewPlugin, P>> {
181 HttpPlugins(PluginStack::new(new_plugin, self.0))
182 }
183
184 /// Applies a single [`tower::Layer`] to all operations _before_ they are deserialized.
185 pub fn layer<L>(self, layer: L) -> HttpPlugins<PluginStack<LayerPlugin<L>, P>> {
186 HttpPlugins(PluginStack::new(LayerPlugin(layer), self.0))
187 }
188}
189
190impl<Ser, Op, T, InnerPlugin> Plugin<Ser, Op, T> for HttpPlugins<InnerPlugin>
191where
192 InnerPlugin: Plugin<Ser, Op, T>,
193{
194 type Output = InnerPlugin::Output;
195
196 fn apply(&self, input: T) -> Self::Output {
197 self.0.apply(input)
198 }
199}
200
201impl<InnerPlugin> HttpMarker for HttpPlugins<InnerPlugin> where InnerPlugin: HttpMarker {}