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};
use pavex::request::RequestHead;
// [...]
impl UserAgent {
pub fn extract(request_head: &RequestHead) -> Result<Self, ToStrError /* (1)! */> {
let Some(user_agent) = request_head.headers.get(USER_AGENT) else {
return Ok(UserAgent::Unknown);
};
user_agent.to_str().map(|s| UserAgent::Known(s.into()))
}
}
ToStrError
is the error type byHeaderValue
'sto_str
when there is a non-printable ASCII character in the header.
All errors must be handled
If you try to build the project now, you'll get an error from Pavex:
ERROR:
× You registered a constructor that returns a `Result`, but you did not
│ register an error handler for it. If I don't have an error handler, I
│ don't know what to do with the error when the constructor fails!
│
│ ╭─[app/src/blueprint.rs:12:1]
│ 12 │ configuration::register(&mut bp);
│ 13 │ bp.request_scoped(f!(crate::user_agent::UserAgent::extract));
│ · ────────────────────┬────────────────────
│ · The fallible constructor was registered here
│ 14 │
│ ╰────
│ help: Add an error handler via `.error_handler`
The invocation of `pavex [...] generate [...]` exited with a non-zero status code: 1
error: Failed to run `bp`, the code generator for package `server_sdk`
Pavex is complaining: you can register a fallible constructor, but you must also register an error handler for it.
Add an error handler
An error handler must convert a reference to the error type into a Response
(1).
It decouples 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.
- Error handlers, just like request handlers 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.
Define a new invalid_user_agent
function in app/src/user_agent.rs
:
// [...]
pub fn invalid_user_agent(_e: &ToStrError) -> Response {
Response::bad_request()
.set_typed_body("The `User-Agent` header value can only use ASCII printable characters.")
}
Then register the error handler with the Blueprint
:
// [...]
pub fn blueprint() -> Blueprint {
// [...]
bp.request_scoped(f!(crate::user_agent::UserAgent::extract))
.error_handler(f!(crate::user_agent::invalid_user_agent));
// [...]
}
The application should compile successfully now.