aws_smithy_http_server/plugin/
mod.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! The plugin system allows you to build middleware with an awareness of the operation it is applied to.
7//!
8//! The system centers around the [`Plugin`], [`HttpMarker`], and [`ModelMarker`] traits. In
9//! addition, this module provides helpers for composing and combining [`Plugin`]s.
10//!
11//! # HTTP plugins vs model plugins
12//!
13//! Plugins come in two flavors: _HTTP_ plugins and _model_ plugins. The key difference between
14//! them is _when_ they run:
15//!
16//! - A HTTP plugin acts on the HTTP request before it is deserialized, and acts on the HTTP response
17//!   after it is serialized.
18//! - A model plugin acts on the modeled operation input after it is deserialized, and acts on the
19//!   modeled operation output or the modeled operation error before it is serialized.
20//!
21//! See the relevant section in [the book], which contains an illustrative diagram.
22//!
23//! Both kinds of plugins implement the [`Plugin`] trait, but only HTTP plugins implement the
24//! [`HttpMarker`] trait and only model plugins implement the [`ModelMarker`] trait. There is no
25//! difference in how an HTTP plugin or a model plugin is applied, so both the [`HttpMarker`] trait
26//! and the [`ModelMarker`] trait are _marker traits_, they carry no behavior. Their only purpose
27//! is to mark a plugin, at the type system leve, as allowed to run at a certain time. A plugin can be
28//! _both_ a HTTP plugin and a model plugin by implementing both traits; in this case, when the
29//! plugin runs is decided by you when you register it in your application. [`IdentityPlugin`],
30//! [`Scoped`], and [`LayerPlugin`] are examples of plugins that implement both traits.
31//!
32//! In practice, most plugins are HTTP plugins. Since HTTP plugins run before a request has been
33//! correctly deserialized, HTTP plugins should be fast and lightweight. Only use model plugins if
34//! you absolutely require your middleware to run after deserialization, or to act on particular
35//! fields of your deserialized operation's input/output/errors.
36//!
37//! [the book]: https://smithy-lang.github.io/smithy-rs/design/server/anatomy.html
38//!
39//! # Filtered application of a HTTP [`Layer`](tower::Layer)
40//!
41//! ```
42//! # use aws_smithy_http_server::plugin::*;
43//! # use aws_smithy_http_server::scope;
44//! # use aws_smithy_http_server::shape_id::ShapeId;
45//! # let layer = ();
46//! # #[derive(PartialEq)]
47//! # enum Operation { GetPokemonSpecies }
48//! # struct GetPokemonSpecies;
49//! # impl GetPokemonSpecies { const ID: ShapeId = ShapeId::new("namespace#name", "namespace", "name"); };
50//! // Create a `Plugin` from a HTTP `Layer`
51//! let plugin = LayerPlugin(layer);
52//!
53//! scope! {
54//!     struct OnlyGetPokemonSpecies {
55//!         includes: [GetPokemonSpecies],
56//!         excludes: [/* The rest of the operations go here */]
57//!     }
58//! }
59//!
60//! // Only apply the layer to operations with name "GetPokemonSpecies".
61//! let filtered_plugin = Scoped::new::<OnlyGetPokemonSpecies>(&plugin);
62//!
63//! // The same effect can be achieved at runtime.
64//! let filtered_plugin = filter_by_operation(&plugin, |operation: Operation| operation == Operation::GetPokemonSpecies);
65//! ```
66//!
67//! # Construct a [`Plugin`] from a closure that takes as input the operation name
68//!
69//! ```rust
70//! # use aws_smithy_http_server::{service::*, operation::OperationShape, plugin::Plugin, shape_id::ShapeId};
71//! # pub enum Operation { CheckHealth, GetPokemonSpecies }
72//! # impl Operation { fn shape_id(&self) -> ShapeId { ShapeId::new("", "", "") }}
73//! # pub struct CheckHealth;
74//! # pub struct GetPokemonSpecies;
75//! # pub struct PokemonService;
76//! # impl ServiceShape for PokemonService {
77//! #   const ID: ShapeId = ShapeId::new("", "", "");
78//! #   const VERSION: Option<&'static str> = None;
79//! #   type Protocol = ();
80//! #   type Operations = Operation;
81//! # }
82//! # impl OperationShape for CheckHealth { const ID: ShapeId = ShapeId::new("", "", ""); type Input = (); type Output = (); type Error = (); }
83//! # impl OperationShape for GetPokemonSpecies { const ID: ShapeId = ShapeId::new("", "", ""); type Input = (); type Output = (); type Error = (); }
84//! # impl ContainsOperation<CheckHealth> for PokemonService { const VALUE: Operation = Operation::CheckHealth; }
85//! # impl ContainsOperation<GetPokemonSpecies> for PokemonService { const VALUE: Operation = Operation::GetPokemonSpecies; }
86//! use aws_smithy_http_server::plugin::plugin_from_operation_fn;
87//! use tower::layer::layer_fn;
88//!
89//! struct FooService<S> {
90//!     info: String,
91//!     inner: S
92//! }
93//!
94//! fn map<S>(op: Operation, inner: S) -> FooService<S> {
95//!     match op {
96//!         Operation::CheckHealth => FooService { info: op.shape_id().name().to_string(), inner },
97//!         Operation::GetPokemonSpecies => FooService { info: "bar".to_string(), inner },
98//!         _ => todo!()
99//!     }
100//! }
101//!
102//! // This plugin applies the `FooService` middleware around every operation.
103//! let plugin = plugin_from_operation_fn(map);
104//! # let _ = Plugin::<PokemonService, CheckHealth, ()>::apply(&plugin, ());
105//! # let _ = Plugin::<PokemonService, GetPokemonSpecies, ()>::apply(&plugin, ());
106//! ```
107//!
108//! # Combine [`Plugin`]s
109//!
110//! ```no_run
111//! # use aws_smithy_http_server::plugin::*;
112//! # struct Foo;
113//! # impl HttpMarker for Foo { }
114//! # let a = Foo; let b = Foo;
115//! // Combine `Plugin`s `a` and `b`. Both need to implement `HttpMarker`.
116//! let plugin = HttpPlugins::new()
117//!     .push(a)
118//!     .push(b);
119//! ```
120//!
121//! As noted in the [`HttpPlugins`] documentation, the plugins' runtime logic is executed in registration order,
122//! meaning that `a` is run _before_ `b` in the example above.
123//!
124//! Similarly, you can use [`ModelPlugins`] to combine model plugins.
125//!
126//! # Example implementation of a [`Plugin`]
127//!
128//! The following is an example implementation of a [`Plugin`] that prints out the service's name
129//! and the name of the operation that was hit every time it runs. Since it doesn't act on the HTTP
130//! request nor the modeled operation input/output/errors, this plugin can be both an HTTP plugin
131//! and a model plugin. In practice, however, you'd only want to register it once, as either an
132//! HTTP plugin or a model plugin.
133//!
134//! ```no_run
135//! use aws_smithy_http_server::{
136//!     operation::OperationShape,
137//!     service::ServiceShape,
138//!     plugin::{Plugin, HttpMarker, HttpPlugins, ModelMarker},
139//!     shape_id::ShapeId,
140//! };
141//! # use tower::{layer::util::Stack, Layer, Service};
142//! # use std::task::{Context, Poll};
143//!
144//! /// A [`Service`] that adds a print log.
145//! #[derive(Clone, Debug)]
146//! pub struct PrintService<S> {
147//!     inner: S,
148//!     service_id: ShapeId,
149//!     operation_id: ShapeId
150//! }
151//!
152//! impl<R, S> Service<R> for PrintService<S>
153//! where
154//!     S: Service<R>,
155//! {
156//!     type Response = S::Response;
157//!     type Error = S::Error;
158//!     type Future = S::Future;
159//!
160//!     fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
161//!         self.inner.poll_ready(cx)
162//!     }
163//!
164//!     fn call(&mut self, req: R) -> Self::Future {
165//!         println!("Hi {} in {}", self.operation_id.absolute(), self.service_id.absolute());
166//!         self.inner.call(req)
167//!     }
168//! }
169//!
170//! /// A [`Plugin`] for a service builder to add a [`PrintLayer`] over operations.
171//! #[derive(Debug)]
172//! pub struct PrintPlugin;
173//!
174//! impl<Ser, Op, T> Plugin<Ser, Op, T> for PrintPlugin
175//! where
176//!     Ser: ServiceShape,
177//!     Op: OperationShape,
178//! {
179//!     type Output = PrintService<T>;
180//!
181//!     fn apply(&self, inner: T) -> Self::Output {
182//!         PrintService {
183//!             inner,
184//!             service_id: Op::ID,
185//!             operation_id: Ser::ID,
186//!         }
187//!     }
188//! }
189//!
190//! // This plugin could be registered as an HTTP plugin and a model plugin, so we implement both
191//! // marker traits.
192//!
193//! impl HttpMarker for PrintPlugin { }
194//! impl ModelMarker for PrintPlugin { }
195//! ```
196
197mod closure;
198pub(crate) mod either;
199mod filter;
200mod http_plugins;
201mod identity;
202mod layer;
203mod model_plugins;
204#[doc(hidden)]
205pub mod scoped;
206mod stack;
207
208pub use closure::{plugin_from_operation_fn, OperationFn};
209pub use either::Either;
210pub use filter::{filter_by_operation, FilterByOperation};
211pub use http_plugins::HttpPlugins;
212pub use identity::IdentityPlugin;
213pub use layer::{LayerPlugin, PluginLayer};
214pub use model_plugins::ModelPlugins;
215pub use scoped::Scoped;
216pub use stack::PluginStack;
217
218/// A mapping from one [`Service`](tower::Service) to another. This should be viewed as a
219/// [`Layer`](tower::Layer) parameterized by the protocol and operation.
220///
221/// The generics `Ser` and `Op` allow the behavior to be parameterized by the [Smithy service] and
222/// [operation] it's applied to.
223///
224/// See [module](crate::plugin) documentation for more information.
225///
226/// [Smithy service]: https://smithy.io/2.0/spec/service-types.html#service
227/// [operation]: https://smithy.io/2.0/spec/service-types.html#operation
228pub trait Plugin<Ser, Op, T> {
229    /// The type of the new [`Service`](tower::Service).
230    type Output;
231
232    /// Maps a [`Service`](tower::Service) to another.
233    fn apply(&self, input: T) -> Self::Output;
234}
235
236impl<'a, Ser, Op, T, Pl> Plugin<Ser, Op, T> for &'a Pl
237where
238    Pl: Plugin<Ser, Op, T>,
239{
240    type Output = Pl::Output;
241
242    fn apply(&self, inner: T) -> Self::Output {
243        <Pl as Plugin<Ser, Op, T>>::apply(self, inner)
244    }
245}
246
247/// A HTTP plugin is a plugin that acts on the HTTP request before it is deserialized, and acts on
248/// the HTTP response after it is serialized.
249///
250/// This trait is a _marker_ trait to indicate that a plugin can be registered as an HTTP plugin.
251///
252/// Compare with [`ModelMarker`] in the [module](crate::plugin) documentation, which contains an
253/// example implementation too.
254pub trait HttpMarker {}
255impl<'a, Pl> HttpMarker for &'a Pl where Pl: HttpMarker {}
256
257/// A model plugin is a plugin that acts on the modeled operation input after it is deserialized,
258/// and acts on the modeled operation output or the modeled operation error before it is
259/// serialized.
260///
261/// This trait is a _marker_ trait to indicate that a plugin can be registered as a model plugin.
262///
263/// Compare with [`HttpMarker`] in the [module](crate::plugin) documentation.
264///
265/// # Example implementation of a model plugin
266///
267/// Model plugins are most useful when you really need to rely on the actual shape of your modeled
268/// operation input, operation output, and/or operation errors. For this reason, most (but not all)
269/// model plugins are _operation-specific_: somewhere in the type signature of their definition,
270/// they'll rely on a particular operation shape's types. It is therefore important that you scope
271/// application of model plugins to the operations they are meant to work on, via
272/// [`Scoped`] or [`filter_by_operation`].
273///
274/// Below is an example implementation of a model plugin that can only be applied to the
275/// `CheckHealth` operation: note how in the `Service` trait implementation, we require access to
276/// the operation's input, where we log the `health_info` field.
277///
278/// ```no_run
279/// use std::marker::PhantomData;
280///
281/// use aws_smithy_http_server::{operation::OperationShape, plugin::{ModelMarker, Plugin}};
282/// use tower::Service;
283/// # pub struct SimpleService;
284/// # pub struct CheckHealth;
285/// # pub struct CheckHealthInput {
286/// #     health_info: (),
287/// # }
288/// # pub struct CheckHealthOutput;
289/// # impl aws_smithy_http_server::operation::OperationShape for CheckHealth {
290/// #     const ID: aws_smithy_http_server::shape_id::ShapeId = aws_smithy_http_server::shape_id::ShapeId::new(
291/// #         "com.amazonaws.simple#CheckHealth",
292/// #         "com.amazonaws.simple",
293/// #         "CheckHealth",
294/// #     );
295/// #     type Input = CheckHealthInput;
296/// #     type Output = CheckHealthOutput;
297/// #     type Error = std::convert::Infallible;
298/// # }
299///
300/// /// A model plugin that can only be applied to the `CheckHealth` operation.
301/// pub struct CheckHealthPlugin<Exts> {
302///     pub _exts: PhantomData<Exts>,
303/// }
304///
305/// impl<Exts> CheckHealthPlugin<Exts> {
306///     pub fn new() -> Self {
307///         Self { _exts: PhantomData }
308///     }
309/// }
310///
311/// impl<T, Exts> Plugin<SimpleService, CheckHealth, T> for CheckHealthPlugin<Exts> {
312///     type Output = CheckHealthService<T, Exts>;
313///
314///     fn apply(&self, input: T) -> Self::Output {
315///         CheckHealthService {
316///             inner: input,
317///             _exts: PhantomData,
318///         }
319///     }
320/// }
321///
322/// impl<Exts> ModelMarker for CheckHealthPlugin<Exts> { }
323///
324/// #[derive(Clone)]
325/// pub struct CheckHealthService<S, Exts> {
326///     inner: S,
327///     _exts: PhantomData<Exts>,
328/// }
329///
330/// impl<S, Exts> Service<(<CheckHealth as OperationShape>::Input, Exts)> for CheckHealthService<S, Exts>
331/// where
332///     S: Service<(<CheckHealth as OperationShape>::Input, Exts)>,
333/// {
334///     type Response = S::Response;
335///     type Error = S::Error;
336///     type Future = S::Future;
337///
338///     fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
339///         self.inner.poll_ready(cx)
340///     }
341///
342///     fn call(&mut self, req: (<CheckHealth as OperationShape>::Input, Exts)) -> Self::Future {
343///         let (input, _exts) = &req;
344///
345///         // We have access to `CheckHealth`'s modeled operation input!
346///         dbg!(&input.health_info);
347///
348///         self.inner.call(req)
349///     }
350/// }
351///
352/// // In `main.rs` or wherever we register plugins, we have to make sure we only apply this plugin
353/// // to the the only operation it can be applied to, the `CheckHealth` operation. If we apply the
354/// // plugin to other operations, we will get a compilation error.
355///
356/// use aws_smithy_http_server::plugin::Scoped;
357/// use aws_smithy_http_server::scope;
358///
359/// pub fn main() {
360///     scope! {
361///         struct OnlyCheckHealth {
362///             includes: [CheckHealth],
363///             excludes: [/* The rest of the operations go here */]
364///         }
365///     }
366///
367///     let model_plugin = CheckHealthPlugin::new();
368///     # _foo(&model_plugin);
369///
370///     // Scope the plugin to the `CheckHealth` operation.
371///     let scoped_plugin = Scoped::new::<OnlyCheckHealth>(model_plugin);
372///     # fn _foo(model_plugin: &CheckHealthPlugin<()>) {}
373/// }
374/// ```
375///
376/// If you are a service owner and don't care about giving a name to the model plugin, you can
377/// simplify this down to:
378///
379/// ```no_run
380/// use std::marker::PhantomData;
381///
382/// use aws_smithy_http_server::operation::OperationShape;
383/// use tower::Service;
384/// # pub struct SimpleService;
385/// # pub struct CheckHealth;
386/// # pub struct CheckHealthInput {
387/// #     health_info: (),
388/// # }
389/// # pub struct CheckHealthOutput;
390/// # impl aws_smithy_http_server::operation::OperationShape for CheckHealth {
391/// #     const ID: aws_smithy_http_server::shape_id::ShapeId = aws_smithy_http_server::shape_id::ShapeId::new(
392/// #         "com.amazonaws.simple#CheckHealth",
393/// #         "com.amazonaws.simple",
394/// #         "CheckHealth",
395/// #     );
396/// #     type Input = CheckHealthInput;
397/// #     type Output = CheckHealthOutput;
398/// #     type Error = std::convert::Infallible;
399/// # }
400///
401/// #[derive(Clone)]
402/// pub struct CheckHealthService<S, Exts> {
403///     inner: S,
404///     _exts: PhantomData<Exts>,
405/// }
406///
407/// impl<S, Exts> Service<(<CheckHealth as OperationShape>::Input, Exts)> for CheckHealthService<S, Exts>
408/// where
409///     S: Service<(<CheckHealth as OperationShape>::Input, Exts)>,
410/// {
411///     type Response = S::Response;
412///     type Error = S::Error;
413///     type Future = S::Future;
414///
415///     fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
416///         self.inner.poll_ready(cx)
417///     }
418///
419///     fn call(&mut self, req: (<CheckHealth as OperationShape>::Input, Exts)) -> Self::Future {
420///         let (input, _exts) = &req;
421///
422///         // We have access to `CheckHealth`'s modeled operation input!
423///         dbg!(&input.health_info);
424///
425///         self.inner.call(req)
426///     }
427/// }
428///
429/// // In `main.rs`:
430///
431/// use aws_smithy_http_server::plugin::LayerPlugin;
432/// use aws_smithy_http_server::plugin::Scoped;
433/// use aws_smithy_http_server::scope;
434///
435/// fn new_check_health_service<S, Ext>(inner: S) -> CheckHealthService<S, Ext> {
436///     CheckHealthService {
437///         inner,
438///         _exts: PhantomData,
439///     }
440/// }
441///
442/// pub fn main() {
443///     scope! {
444///         struct OnlyCheckHealth {
445///             includes: [CheckHealth],
446///             excludes: [/* The rest of the operations go here */]
447///         }
448///     }
449///
450///     # fn new_check_health_service(inner: ()) -> CheckHealthService<(), ()> {
451///     #     CheckHealthService {
452///     #         inner,
453///     #         _exts: PhantomData,
454///     #     }
455///     # }
456///     let layer = tower::layer::layer_fn(new_check_health_service);
457///     let model_plugin = LayerPlugin(layer);
458///
459///     // Scope the plugin to the `CheckHealth` operation.
460///     let scoped_plugin = Scoped::new::<OnlyCheckHealth>(model_plugin);
461/// }
462/// ```
463pub trait ModelMarker {}
464impl<'a, Pl> ModelMarker for &'a Pl where Pl: ModelMarker {}