Skip to main content

Errors

Actix Web uses its own actix_web::error::Error type and actix_web::error::ResponseError trait for error handling from web handlers.

If a handler returns an Error (referring to the general Rust trait std::error::Error) in a Result that also implements the ResponseError trait, Actix Web will render that error as an HTTP response with its corresponding actix_web::http::StatusCode. An internal server error is generated by default:

pub trait ResponseError {
fn error_response(&self) -> HttpResponse<BoxBody>;
fn status_code(&self) -> StatusCode;
}

A Responder coerces compatible Results into HTTP responses:

impl<T: Responder, E: Into<Error>> Responder for Result<T, E>

Error in the code above is actix-web's error definition, and any errors that implement ResponseError can be converted to one automatically.

Actix Web provides ResponseError implementations for some common non-actix errors. For example, if a handler responds with an io::Error, that error is converted into an HttpInternalServerError:

use std::io;
use actix_files::NamedFile;

fn index(_req: HttpRequest) -> io::Result<NamedFile> {
Ok(NamedFile::open("static/index.html")?)
}

See the Actix Web API documentation for a full list of foreign implementations for ResponseError.

An example of a custom error response

Here's an example implementation for ResponseError, using the derive_more crate for declarative error enums.

use actix_web::{error, Result};
use derive_more::{Display, Error};

#[derive(Debug, Display, Error)]
#[display(fmt = "my error: {}", name)]
struct MyError {
name: &'static str,
}

// Use default implementation for `error_response()` method
impl error::ResponseError for MyError {}

async fn index() -> Result<&'static str, MyError> {
Err(MyError { name: "test" })
}

ResponseError 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:

use actix_web::{
error, get,
http::{header::ContentType, StatusCode},
App, HttpResponse,
};
use derive_more::{Display, Error};

#[derive(Debug, Display, Error)]
enum MyError {
#[display(fmt = "internal error")]
InternalError,

#[display(fmt = "bad request")]
BadClientData,

#[display(fmt = "timeout")]
Timeout,
}

impl error::ResponseError for MyError {
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code())
.insert_header(ContentType::html())
.body(self.to_string())
}

fn status_code(&self) -> StatusCode {
match *self {
MyError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
MyError::BadClientData => StatusCode::BAD_REQUEST,
MyError::Timeout => StatusCode::GATEWAY_TIMEOUT,
}
}
}

#[get("/")]
async fn index() -> Result<&'static str, MyError> {
Err(MyError::BadClientData)
}

Error helpers

Actix Web provides a set of error helper functions that are useful for generating specific HTTP error codes from other errors. Here we convert MyError, which doesn't implement the ResponseError trait, to a 400 (bad request) using map_err:

use actix_web::{error, get, App, HttpServer};

#[derive(Debug)]
struct MyError {
name: &'static str,
}

#[get("/")]
async fn index() -> actix_web::Result<String> {
let result = Err(MyError { name: "test error" });

result.map_err(|err| error::ErrorBadRequest(err.name))
}

See the API documentation for actix-web's error module for a full list of available error helpers.

Error logging

Actix logs all errors at the WARN log level. If an application's log level is set to DEBUG and RUST_BACKTRACE is enabled, the backtrace is also logged. These are configurable with environmental variables:

>> RUST_BACKTRACE=1 RUST_LOG=actix_web=debug cargo run

The Error type uses the cause's error backtrace if available. If the underlying failure does not provide a backtrace, a new backtrace is constructed pointing to the point where the conversion occurred (rather than the origin of the error).

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:

use actix_web::{
error, get,
http::{header::ContentType, StatusCode},
App, HttpResponse, HttpServer,
};
use derive_more::{Display, Error};

#[derive(Debug, Display, Error)]
enum UserError {
#[display(fmt = "Validation error on field: {}", field)]
ValidationError { field: String },
}

impl error::ResponseError for UserError {
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code())
.insert_header(ContentType::html())
.body(self.to_string())
}
fn status_code(&self) -> StatusCode {
match *self {
UserError::ValidationError { .. } => StatusCode::BAD_REQUEST,
}
}
}

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:

vuse actix_web::{
error, get,
http::{header::ContentType, StatusCode},
App, HttpResponse, HttpServer,
};
use derive_more::{Display, Error};

#[derive(Debug, Display, Error)]
enum UserError {
#[display(fmt = "An internal error occurred. Please try again later.")]
InternalError,
}

impl error::ResponseError for UserError {
fn error_response(&self) -> HttpResponse {
HttpResponse::build(self.status_code())
.insert_header(ContentType::html())
.body(self.to_string())
}

fn status_code(&self) -> StatusCode {
match *self {
UserError::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}

#[get("/")]
async fn index() -> Result<&'static str, UserError> {
do_thing_that_fails().map_err(|_e| UserError::InternalError)?;
Ok("success!")
}

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.8"
log = "0.4"
use actix_web::{error, get, middleware::Logger, App, HttpServer, Result};
use derive_more::{Display, Error};
use log::info;

#[derive(Debug, Display, Error)]
#[display(fmt = "my error: {}", name)]
pub struct MyError {
name: &'static str,
}

// Use default implementation for `error_response()` method
impl error::ResponseError for MyError {}

#[get("/")]
async fn index() -> Result<&'static str, MyError> {
let err = MyError { name: "test error" };
info!("{}", err);
Err(err)
}

#[rustfmt::skip]
#[actix_web::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "info");
std::env::set_var("RUST_BACKTRACE", "1");
env_logger::init();

HttpServer::new(|| {
let logger = Logger::default();

App::new()
.wrap(logger)
.service(index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}