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