Skip to content

Pre-processing

Pre-processing middlewares execute ahead of the route handler.
They can enforce pre-conditions and return an early response if they are not met, skipping the rest of the request processing pipeline. E.g., rejecting unauthenticated requests or enforcing rate limits.

Defining pre-processing middlewares

Use the #[pre_process] attribute to define a new pre-processing middleware:

use pavex::Response;
use pavex::http::{HeaderValue, header::LOCATION};
use pavex::middleware::Processing;
use pavex::pre_process;
use pavex::request::RequestHead;

/// If the request path ends with a `/`,
/// redirect to the same path without the trailing `/`.
#[pre_process]
pub fn redirect_to_normalized(request_head: &RequestHead) -> Processing {
    let Some(normalized_path) = request_head.target.path().strip_suffix('/') else {
        // No need to redirect, we continue processing the request.
        return Processing::Continue;
    };
    let location = HeaderValue::from_str(normalized_path).unwrap();
    let redirect = Response::temporary_redirect().insert_header(LOCATION, location);
    // Short-circuit the request processing pipeline and return a redirect response
    Processing::EarlyReturn(redirect)
}

Signature

The return type of a pre-processing middleware must be one of the following:

Other than that, you have a lot of freedom in how you define your pre-processing middlewares:

Registration

Invoke Blueprint::pre_process to register a pre-processing middleware:

use crate::redirect::REDIRECT_TO_NORMALIZED;
use pavex::{Blueprint, blueprint::from};

pub fn blueprint() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.pre_process(REDIRECT_TO_NORMALIZED); // (1)!
    // [...]
}
  1. REDIRECT_TO_NORMALIZED is a strongly-typed constant generated by the #[pre_process] attribute on the redirect_to_normalized function.
    Check out the documentation on component ids for more details.

The middleware will be invoked for all routes registered after it. Check out the scoping section for more details.

Processing

Pre-processing middlewares must return Processing, either directly or as the Ok variant of a Result. Processing is an enum with two variants:

  • Processing::Continue: the middleware has finished its work and the request processing pipeline should continue as usual.
  • Processing::EarlyReturn(T): the remaining pre-processing and wrapping middlewares won't be invoked, nor will the route handler. T will be converted into a response, fed to the post-processing middlewares (if there are any) and then sent back to the client.

Check out the execution order guide for more details on how the different types of middlewares interact with each other.

T, the type inside Processing::EarlyReturn, must implement the IntoResponse trait. If T is not specified, it defaults to Response.

Middlewares can fail

Pre-processing middlewares can return a Result.

use crate::errors::AuthError;
use pavex::middleware::Processing;
use pavex::pre_process;
use pavex::request::RequestHead;

#[pre_process]
pub fn reject_anonymous(request: &RequestHead) -> Result<Processing, AuthError> {
    if request.headers.get("Authorization").is_none() {
        return Err(AuthError);
    }
    Ok(Processing::Continue)
}

Check out the error handling guide for more details on how to handle the error case.

Result or Processing::EarlyReturn?

The rest of the request processing pipeline will be skipped if your pre-processing middleware returns an error.

Why does Processing::EarlyReturn exist then? Can't I just return an error when I want to skip the rest of the pipeline?

You can, but an error has a different semantic meaning.
An error is a problem that occurred during the processing of the request. Pavex will invoke error observers, if they were registered, and your application will probably emit error-level logs, increment error counters, etc.

There are scenarios where you want to return an early response, but it's not an error. E.g., you might want to redirect all requests with a trailing slash to the same URL without the trailing slash. An early return is an expected scenario, not an error.

Choose the short-circuiting mechanism that best fits the semantics of your use case.

Dependency injection

Pre-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.

Pre-processing middlewares, like request handlers and post-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.