Errors
ntex uses the ntex::web::WebResponseError trait for error handling from web handlers.
If a handler returns an error (any type implementing the general Rust trait std::error::Error) in a Result and that error also implements the WebResponseError trait, ntex will render that error as an HTTP response. An internal server error is generated by default:
pub trait WebResponseError<St, Err>: std::error::Error + 'static {
fn error_response(&self, state: &St) -> HttpResponse;
}
St is the application state type and Err is the application's error domain, defined by the state's web::State::Error type. The default error domain is web::DefaultError. To make an error usable with any application state, implement WebResponseError<St, web::DefaultError> for all St.
A Responder coerces compatible Results into HTTP responses:
impl<St, T, E> Responder<St> for Result<T, E>
where
St: web::State,
T: Responder<St>,
E: WebResponseError<St, St::Error>,
Errors from services and middleware are stored in the type-erased web::WebError type. Any error that implements WebResponseError can be converted to one automatically.
ntex provides WebResponseError implementations for some common non-ntex errors. For example, if a handler responds with an io::Error, that error is converted into an internal server error response:
use std::io;
use ntex_files::NamedFile;
async fn index(_req: HttpRequest) -> io::Result<NamedFile> {
Ok(NamedFile::open("static/index.html")?)
}
See the ntex API documentation for a full list of foreign implementations for WebResponseError.
An example of a custom error response
Here's an example implementation for WebResponseError, using the derive_more crate for declarative error enums.
WebResponseError has a default implementation for error_response() that will render a 500 (internal server error), and that's what will happen when the index handler executes above.
Override error_response() to produce more useful results:
Error helpers
ntex provides a set of error helper functions that are useful for generating specific HTTP error codes from other errors. Each helper wraps the value into an InternalError. Here we convert MyError, which doesn't implement the WebResponseError trait, to a 400 (bad request) using map_err:
See the API documentation for ntex's error module for a full list of available error helpers.
Error logging
ntex logs errors that are rendered as 500 (internal server error) responses at the ERROR log level, all other errors are logged at the DEBUG log level. Logging is configurable with environmental variables:
>> RUST_LOG=ntex=debug cargo run
Recommended practices in error handling
It might be useful to think about dividing the errors an application produces into two broad groups: those which are intended to be user-facing, and those which are not.
An example of the former is that I might use failure to specify a UserError enum which encapsulates a ValidationError to return whenever a user sends bad input:
This will behave exactly as intended because the error message defined with display is written with the explicit intent to be read by a user.
However, sending back an error's message isn't desirable for all errors -- there are many failures that occur in a server environment where we'd probably want the specifics to be hidden from the user. For example, if a database goes down and client libraries start producing connect timeout errors, or if an HTML template was improperly formatted and errors when rendered. In these cases, it might be preferable to map the errors to a generic error suitable for user consumption.
Here's an example that maps an internal error to a user-facing InternalError with a custom message:
By dividing errors into those which are user facing and those which are not, we can ensure that we don't accidentally expose users to errors thrown by application internals which they weren't meant to see.
Error Logging
This is a basic example using middleware::Logger which depends on env_logger and log:
[dependencies]
env_logger = "0.11"
log = "0.4"