Skip to content

Error handlers

Error handlers turn errors into HTTP responses.
They are a mechanism to decouple what went wrong from the way it's communicated to the caller.

Registration

You must specify an error handler every time you register a fallible component (request handler, constructor, middleware).

src/core/blueprint.rs
use pavex::blueprint::router::POST;
use pavex::blueprint::Blueprint;
use pavex::f;

pub fn blueprint() -> Blueprint {
    let mut bp = Blueprint::new();
    bp.route(POST, "/login", f!(super::handler))
        .error_handler(f!(super::login_error2response)); // (1)!
        // [...]
}
  1. Pavex will return an error during code generation if you register an error handler for an infallible component.

You must provide an unambiguous path to the error handler, wrapped in the f! macro.

Registration syntax

You can use free functions, static methods, non-static methods, and trait methods as error handlers. Check out the dependency injection cookbook for more details on the syntax for each case.

IntoResponse

Error handlers, like request handlers and middlewares, must return a type that can be converted into a Response via the IntoResponse trait.

src/core/error_handler.rs
use pavex::http::StatusCode;

pub async fn login_error2response(e: &LoginError) -> StatusCode {
    match e {
        LoginError::InvalidCredentials => StatusCode::UNAUTHORIZED,
        LoginError::DatabaseError => StatusCode::INTERNAL_SERVER_ERROR,
    }
}

Pavex implements IntoResponse for StatusCode, thus it's an acceptable return type for an error handler.
If you want to return a custom type from your error handler, you must implement IntoResponse for it.

Error reference

Error handlers must take a reference (&) to the error as one of their input parameters.
What should the error type be?

Let's look at an example: you want to register an error handler for the following request handler, login.

src/core/routes.rs
pub fn handler(head: &RequestHead) -> Result<Response, LoginError /* (1)! */> {
    // Handler logic...
    // [...]
}
  1. The request handler is fallible because it returns a Result, with LoginError as its error type.

Specialized

Your error handler can be specialized, thus taking &LoginError as one of its input parameters:

src/core/error_handler.rs
use pavex::http::StatusCode;

pub async fn login_error2response(e: &LoginError) -> StatusCode {
    match e {
        LoginError::InvalidCredentials => StatusCode::UNAUTHORIZED,
        LoginError::DatabaseError => StatusCode::INTERNAL_SERVER_ERROR,
    }
}

A specialized error handler is the best choice when you want to leverage the information encoded in the error type to customize the response returned to the caller.
In the example above, we are matching on the error variants to choose the most appropriate status code.

Universal

You can also opt for a universal error handler. Instead of &LoginError, you use &pavex::Error as one of its input parameters:

src/universal/error_handler.rs
use pavex::response::Response;

pub async fn login_error2response(e: &pavex::Error) -> Response {
    Response::unauthorized().set_typed_body(e.to_string())
}

Pavex will automatically convert LoginError into pavex::Error before invoking your universal error handler.

Universal error handlers are convenient when you want to return a non-specific response.
In the example above, we are always returning the same status code, with error information encoded in the body.

Error handlers can't fail

Error handlers must be infallible—i.e. they can't return a Result.
Error handlers perform a conversion. The error type should contain all the information required to build the HTTP response. If that's not the case, you may be tempted to perform fallible operations in the error handler to enrich the error type. Resist the temptation!
Instead, rework the fallible component to add the missing details to the error type, so that the error handler can be infallible.

Dependency injection

Error handlers can take advantage of dependency injection.

You must specify the dependencies of your error handler 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.

Check out the dependency injection guide for more details on how the process works.

Sync or async?

Error handlers are commonly synchronous, but Pavex supports asynchronous error handlers as well.
Check out the "Sync vs async" guide for more details on the differences between the two and how to choose the right one for your use case.