Post-processing
Post-processing middlewares are invoked after the request handler.
They are suitable for modifying the response and/or performing side-effects based on its contents.
E.g. logging the response's status code, injecting response headers, etc.
use pavex::{response::Response};
use pavex_tracing::{
RootSpan,
fields::{http_response_status_code, HTTP_RESPONSE_STATUS_CODE}
};
pub fn response_logger(response: Response, root_span: &RootSpan) -> Response
{
root_span.record(
HTTP_RESPONSE_STATUS_CODE,
http_response_status_code(&response),
);
response
}
Signature
Pavex accepts a wide range of function signatures for post-processing middlewares. There are two constraints:
- They must take a
Response
as one of their input parameters. - They must return a type that can be converted into a
Response
via theIntoResponse
trait.
If fallible, they can return aResult
with a type that implementsIntoResponse
as itsOk
variant.
Other than that, you have a lot of freedom in how you define your post-processing middlewares:
- They can be free functions or methods.
- They can be synchronous or asynchronous.
- They can take additional input parameters, leaning on Pavex's dependency injection system.
- If fallible, they can use whatever error type you prefer, as long as you provide an error handler for it.
The next sections of this guide will elaborate on each of these points.
Registration
You register a post-processing middleware against a blueprint via the post_process
method.
use pavex::blueprint::router::GET;
use pavex::blueprint::Blueprint;
use pavex::f;
pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.post_process(f!(super::response_logger));
bp.route(GET, "/", f!(super::handler));
bp
}
You must provide an unambiguous path to the middleware, wrapped in the f!
macro.
The middleware will be invoked for all request handlers registered after it, as long as they were registered against the same Blueprint
or one of its nested children.
Check out the scoping section for more details.
Registration syntax
You can use free functions, static methods, non-static methods, and trait methods as middlewares. Check out the dependency injection cookbook for more details on the syntax for each case.
IntoResponse
Post-processing middlewares, like request handlers, must return a type that can be converted into a Response
via the
IntoResponse
trait.
If you want to return a custom type from your middleware, you must implement IntoResponse
for it.
Middlewares can fail
Post-processing middlewares can be fallible, i.e. they can return a Result
.
use pavex::response::Response;
use super::CompressionError;
pub fn compress(response: Response) -> Result<Response, CompressionError>
{
let compressed = {
// Try to compress the response
// [...]
}?;
Ok(compressed)
}
If they do, you must specify an error handler when registering them:
use pavex::blueprint::router::GET;
use pavex::blueprint::Blueprint;
use pavex::f;
pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.post_process(f!(super::compress))
.error_handler(f!(super::compression_error_handler));
// [...]
bp
}
Check out the error handling guide for more details.
Dependency injection
Post-processing middlewares can take advantage of dependency injection.
You must specify the dependencies of your middleware as input parameters in its function signature.
Those inputs are going to be built and injected by the framework, according to the constructors you have registered.
Post-processing middlewares, like request handlers and pre-processing middlewares,
can mutate request-scoped types.
Ask for a &mut
reference to the type you want to mutate as an input parameter, the framework will take care of the rest.
Check out the dependency injection guide for more details
on how the process works.
Check out the request data guide for an overview of the data you can extract from the request
using Pavex's first-party extractors.