Skip to content

Error handling

In UserAgent::extract you're only handling the happy path: the method panics if the User-Agent header contains characters that are not ASCII printable. Panicking for bad user input is poor behavior: you should handle the issue gracefully and return an error instead.

Let's change the signature of UserAgent::extract to return a Result instead:

app/src/user_agent.rs
use pavex::http::header::{ToStrError, USER_AGENT};
// [...]

#[methods]
impl UserAgent {
    #[request_scoped]
    pub fn extract(request_head: &RequestHead) -> Result<Self, ToStrError /* (1)! */> {
        let Some(user_agent) = request_head.headers.get(USER_AGENT) else {
            return Ok(Self::Unknown);
        };

        user_agent.to_str().map(|s| UserAgent::Known(s.into()))
    }
}
  1. ToStrError is the error type by HeaderValue's to_str when there is a non-printable ASCII character in the header.

Error fallbacks

If you try to build the project now, you'll get a warning:

WARNING:
   There is no specific error handler for
   `http::header::ToStrError`, the error returned by one of your
   constructors.
   It'll be converted to `pavex::Error` and handled by the fallback
   error handler.
  
       ╭─[quickstart/project/app/src/user_agent.rs:13:1]
    13 │     impl UserAgent {
    14╭─▶     #[request_scoped]
    15├─▶     pub fn extract(request_head: &RequestHead) -> Result<Self, ToStrError> {
       · ╰──── The fallible constructor was registered here
    16 │             let Some(user_agent) = request_head.headers.get(USER_AGENT) else {
       ╰────
     help: Define an error handler for `http::header::ToStrError`
     help: Add `allow(error_fallback)` to your component's attribute to
           silence this warning

You registered a fallible constructor, but there is no specific error handler for its error type. As it stands, Pavex will invoke the fallback error handler when the constructor fails, returning a generic 500 Internal Server Error response.

Let's handle ToStrError properly.

Add an error handler

An error handler must convert a reference to the error type into a Response1.

Error handlers decouple the detection of an error from its representation on the wire: a constructor doesn't need to know how the error will be represented in the response, it just needs to signal that something went wrong. You can then change the representation of an error on the wire without touching the constructor: you only need to change the error handler.

Define a new invalid_user_agent function in app/src/user_agent.rs:

app/src/user_agent.rs
// [...]
use pavex::{Response, error_handler, methods};
// [...]

#[error_handler]
pub fn invalid_user_agent(_e: &ToStrError) -> Response {
    let body = "The `User-Agent` header value can only use ASCII printable characters.";
    Response::bad_request().set_typed_body(body)
}

The application should compile successfully now.


  1. Error handlers, just like routes and constructors, can take advantage of dependency injection! You could, for example, change the response representation according to the Accept header specified in the request.