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