The following document provides a brief survey of the various positions middleware can be inserted in Smithy Rust.
We use the [Pokémon service](https://github.com/awslabs/smithy-rs/blob/main/codegen-core/common-test-models/pokemon.smithy) as a reference model throughout.
```smithy
/// A Pokémon species forms the basis for at least one Pokémon.
@title("Pokémon Species")
resource PokemonSpecies {
identifiers: {
name: String
},
read: GetPokemonSpecies,
}
/// A users current Pokémon storage.
resource Storage {
identifiers: {
user: String
},
read: GetStorage,
}
/// The Pokémon Service allows you to retrieve information about Pokémon species.
@title("Pokémon Service")
@restJson1
service PokemonService {
version: "2021-12-01",
resources: [PokemonSpecies, Storage],
operations: [
GetServerStatistics,
DoNothing,
CapturePokemon,
CheckHealth
],
}
```
## Introduction to Tower
Smithy Rust is built on top of [`tower`](https://github.com/tower-rs/tower).
> Tower is a library of modular and reusable components for building robust networking clients and servers.
The `tower` library is centered around two main interfaces, the [`Service`](https://docs.rs/tower/latest/tower/trait.Service.html) trait and the [`Layer`](https://docs.rs/tower/latest/tower/trait.Layer.html) trait.
The `Service` trait can be thought of as an asynchronous function from a request to a response, `async fn(Request) -> Result<Response, Error>`, coupled with a mechanism to [handle back pressure](https://docs.rs/tower/latest/tower/trait.Service.html#backpressure), while the `Layer` trait can be thought of as a way of decorating a `Service`, transforming either the request or response.
Middleware in `tower` typically conforms to the following pattern, a `Service` implementation of the form
```rust
pubstructNewService<S>{
inner:S,
/* auxillary data */
}
```
and a complementary
```rust
pubstructNewLayer{
/* auxiliary data */
}
impl<S>Layer<S>forNewLayer{
typeService=NewService<S>;
fnlayer(&self,inner:S)->Self::Service{
NewService{
inner,
/* auxiliary fields */
}
}
}
```
The `NewService` modifies the behavior of the inner `Service``S` while the `NewLayer` takes auxiliary data and constructs `NewService<S>` from `S`.
Customers are then able to stack middleware by composing `Layer`s using combinators such as [`ServiceBuilder::layer`](https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html#method.layer) and [`Stack`](https://docs.rs/tower/latest/tower/layer/util/struct.Stack.html).
<!-- TODO(Update documentation): There's a `Layer` implementation on tuples about to be merged, give it as an example here. -->
## Applying Middleware
One of the primary goals is to provide configurability and extensibility through the application of middleware. The customer is able to apply `Layer`s in a variety of key places during the request/response lifecycle. The following schematic labels each configurable middleware position from A to D:
```mermaid
stateDiagram-v2
state in <<fork>>
state "GetPokemonSpecies" as C1
state "GetStorage" as C2
state "DoNothing" as C3
state "..." as C4
direction LR
[*] --> in : HTTP Request
UpgradeLayer --> [*]: HTTP Response
state A {
state PokemonService {
state RoutingService {
in --> UpgradeLayer: HTTP Request
in --> C2: HTTP Request
in --> C3: HTTP Request
in --> C4: HTTP Request
state B {
state C1 {
state C {
state UpgradeLayer {
direction LR
[*] --> Handler: Model Input
Handler --> [*] : Model Output
state D {
Handler
}
}
}
}
C2
C3
C4
}
}
}
}
C2 --> [*]: HTTP Response
C3 --> [*]: HTTP Response
C4 --> [*]: HTTP Response
```
where `UpgradeLayer` is the `Layer` converting Smithy model structures to HTTP structures and the `RoutingService` is responsible for routing requests to the appropriate operation.
### A) Outer Middleware
The output of the Smithy service builder provides the user with a `Service<http::Request, Response = http::Response>` implementation. A `Layer` can be applied around the entire `Service`.
Note that requests pass through this middleware immediately _after_ routing succeeds and therefore will _not_ be encountered if routing fails. This means that the [TraceLayer](https://docs.rs/tower-http/latest/tower_http/trace/struct.TraceLayer.html) in the example above does _not_ provide logs unless routing has completed. This contrasts to [middleware A](#a-outer-middleware), which _all_ requests/responses pass through when entering/leaving the service.
### C) Operation Specific HTTP Middleware
A "HTTP layer" can be applied to specific operations.
In contrast to [position C](#c-operation-specific-http-middleware), this middleware transforms the operations modelled inputs to modelled outputs.
## Plugin System
Suppose we want to apply a different `Layer` to every operation. In this case, position B (`PokemonService::layer`) will not suffice because it applies a single `Layer` to all routes and while position C (`Operation::layer`) would work, it'd require the customer constructs the `Layer` by hand for every operation.
/// A [`Layer`] which constructs the [`PrintService`].
#[derive(Debug)]
pubstructPrintLayer{
name:&'staticstr,
}
impl<S>Layer<S>forPrintLayer{
typeService=PrintService<S>;
fnlayer(&self,service:S)->Self::Service{
PrintService{
inner:service,
name:self.name,
}
}
}
```
The plugin system provides a way to construct then apply `Layer`s in position [C](#c-operation-specific-http-middleware) and [D](#d-operation-specific-model-middleware), using the [protocol](https://awslabs.github.io/smithy/2.0/aws/protocols/index.html) and [operation shape](https://awslabs.github.io/smithy/2.0/spec/service-types.html#service-operations) as parameters.
An example of a `PrintPlugin` which applies a layer printing the operation name:
```rust
/// A [`Plugin`] for a service builder to add a [`PrintLayer`] over operations.
A `Plugin` can then be applied to all operations using the `Pluggable::apply` method
```rust
pubtraitPluggable<NewPlugin>{
typeOutput;
/// Applies a [`Plugin`] to the service builder.
fnapply(self,plugin:NewPlugin)->Self::Output;
}
```
which is implemented on every service builder.
The plugin system is designed to hide the details of the `Plugin` and `Pluggable` trait from the average consumer. Such customers should instead interact with utility methods on the service builder which are vended by extension traits and enjoy self contained documentation.
```rust
/// An extension trait of [`Pluggable`].
///
/// This provides a [`print`](PrintExt::print) method to all service builders.
pubtraitPrintExt:Pluggable<PrintPlugin>{
/// Causes all operations to print the operation name when called.