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 {}