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:
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()))
}
}
ToStrErroris the error type byHeaderValue'sto_strwhen 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:
// [...]
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.
-
Error handlers, just like routes and constructors, can take advantage of dependency injection! You could, for example, change the response representation according to the
Acceptheader specified in the request. ↩