![`actix_failwrap` banner][banner]
[![Crates.io][crate-badge]][crate]
[![License][license-badge]][license]
[![Docs.rs][docs-badge]][docs]
[![Downloads][downloads-badge]][downloads]
[![Codecov][codecov-badge]][codecov]
[![tests][tests-badge]][codecov]
[![Discord][discord-badge]][discord]
`actix_failwrap` */ˈæk.tɪks ˈfeɪl.ræp/* ("*aktiks fail-rap*") A micro-package that enables ergonomic **error propagation** (via [`thiserror`][thiserror]) inside Actix Web route handlers.
This crate allows you to:
- ✅ Assign HTTP status codes to your [`thiserror`][thiserror] enums.
- 🧩 Customize the HTTP response with a builder function.
- ⚡ Use the [`?`][?] operator naturally inside route handlers.
---
## Table of Contents 📖
- [Features 🚀](#features-)
- [Installation 📦](#installation-)
- [Usage Example 🤔](#usage-example-)
- [Exported macros 🔧](#exported-macros-)
- [`ErrorResponse` ⚙️](#deriveerrorresponse)
- [`proof_route` ⚙️](#proof_route)
- [Security 🔐](#security-)
- [License 📜](#license-)
---
## Features 🚀
- ✅ **Automatic error-to-response conversion** using [`thiserror`][thiserror] enums
Define route errors with `#[derive(ErrorResponse)]` to auto-generate `HttpResponse`.
- 🧩 **Custom response transformation per error enum**
Use `#[transform_response(fn)]` to modify headers, body, or status codes.
- 🧠 **Per-variant status code overrides**
Set status codes using `#[status_code(...)]` — supports both constants and numbers.
- 🔁 **Fallback behavior** for unannotated variants
Variants without `#[status_code]` fall back to `#[default_status_code]` or HTTP 500.
- ✍️ **Extractor error mapping with `#[error_override(...)]`**
Map deserialization or extractor failures to your own enum variant.
- ⚡ **Minimal boilerplate route macros with `#[proof_route(...)]`**
Use `?` with error enums directly and skip [`actix_web`][actix-web] macro imports.
---
## Installation 📦
> [!IMPORTANT]
> The `actix_failwrap` macros rely on [`thiserror`][thiserror] for the `Display` implementation, and are tightly coupled with [`actix-web`][actix-web] for building HTTP responses.
This crate is published on [crates.io]
and is intended for use alongside [`actix-web`][actix-web]
and [`thiserror`][thiserror].
Add all three to your `Cargo.toml`:
```toml
[dependencies]
actix-web = "4"
thiserror = "1"
actix_failwrap = "1.0.0"
```
---
## Usage Example 🤔
This example shows a login route in Actix Web using `actix_failwrap`.
In your project you may have a module that declares models, in this case a `User` model.
In that file you may declare your [`thiserror`][thiserror] error that you may re-use for your handler.
```rust ignore
use serde::{Serialize, Deserialize};
use actix_failwrap::ErrorResponse;
use thiserror::Error;
// Custom error transformation function used by #[transform_response]
// Converts an error into a response with an "Error" header.
fn error_to_header(mut response: HttpResponseBuilder, error: String) -> HttpResponse {
response.insert_header(("Error", error)).finish()
}
// Define a custom error enum for user-related errors.
#[derive(ErrorResponse, Error, Debug)]
// Default fallback for variants without #[status_code],
// if the attribute is not present, the default status code will be 500.
#[default_status_code(InternalServerError)]
// Function used to transform the final HttpResponse,
// if the attribute not present, the Display is mapped to the body.
#[transform_response(error_to_header)]
pub enum UserError {
#[error("Either the email or the password is invalid. Please check the input credentials")]
// This can also be a numeric HTTP status code.
#[status_code(Unauthorized)]
InvalidCredentials,
#[error("Missing credentials, please, introduce your email and password.")]
#[status_code(BadRequest)]
MissingCredentials,
}
#[derive(Serialize, Deserialize)]
pub struct UserCredentials {
pub email: String,
pub password: String,
}
// Simulates a function that attempts to authenticate a user and returns a Result
pub fn obtain_user(credentials: UserCredentials) -> Result<User, UserError> {
/* ... */
}
```
And another module that declares handlers, this example handler obtains a user
with some credentials and returns its JWT token if successful.
```rust ignore
use actix_failwrap::proof_route;
use actix_web::{web::Form, HttpResponse, HttpResponseBuilder};
use crate::models::user::{UserError, UserCredentials, obtain_user};
// Route macro expands to #[actix_web::post("/login")] and allows
// to use `Result<HttpResponse, _>`.
#[proof_route("POST /login")]
async fn post_login(
// If the extractor (Form) fails, override it with MissingCredentials variant
#[error_override(MissingCredentials)] credentials: Form<UserCredentials>
) -> Result<HttpResponse, UserError> {
// Attempt to obtain the user; if it fails, propagate the error
let user = obtain_user(credentials.into_inner())?;
// On success, return a response with a "Login" header containing the JWT
Ok(
HttpResponse::Ok().
.insert_header(("Login", user.jwt()))
.finish()
)
}
```
---
## Exported macros 🔧
This crate exports two macros: `ErrorResponse` and `proof_route`.
### `#[derive(ErrorResponse)]`
Implements `Into<actix_web::HttpResponse>` and `Into<actix_web::Error>` for your `thiserror` enums,
allowing direct propagation with the `?` operator in handlers.
> [!WARNING]
> Requires `#[derive(thiserror::Error)]` because it uses the `Display` implementation.
#### Supported Attributes
- `#[default_status_code(...)]`
Fallback status code used if a variant does not have its own `#[status_code(...)]`
Defaults to `InternalServerError` (500).
- `#[status_code(...)]`
Sets the HTTP status code for a specific variant. Accepts a named status (e.g. `BadRequest`) or number (`400`).
- `#[transform_response(fn)]`
Customizes how the response is built. Takes a function of signature:
`fn(HttpResponseBuilder, String) -> HttpResponse`.
### `#[proof_route(...)]`
Simplifies route definition and error propagation.
```rust ignore
#[proof_route("POST /path")]
```
Expands to:
```rust ignore
#[actix_web::post("/path")]
```
An example function signature looks like
```rust ignore
#[proof_route("GET /users")]
async fn get_users() -> Result<HttpResponse, Error> {}
```
> [!TIP]
> You can use a `Result<T: actix_web::Responder, Error>` instead of `HttpResponse`.
Allows you to:
- Use `Result<HttpResponse, Error>` directly in route bodies.
- Avoid importing `#[post]`, `#[get]`, etc. individually.
- Support extractor error override via `#[error_override(...)]`.
---
## Security 🔐
Security is a top priority for us. If you believe you’ve found a security
vulnerability, **do not open a public issue**. Instead, please read
our [SECURITY.md](./SECURITY.md) policy and report it responsibly by
contacting us at [security@flaky.es].
---
## License 📜
This repository is dual licensed, If your repository is open source, the library is free of use, otherwise contact [licensing@flaky.es] for a custom license for your use case.
For more information read the [license file][license].
[?]: https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.try
[crates.io]: https://crates.io/crates/actix_failwrap
[actix-web]: https://crates.io/crates/actix-web
[thiserror]: https://crates.io/crates/thiserror
[licensing@flaky.es]: mailto:licensing@flaky.es
[security@flaky.es]: mailto:security@flaky.es
[banner]: https://github.com/user-attachments/assets/15d5d3f2-3e78-49f2-8a09-b28b15bedd9f
[crate-badge]: https://badges.ws/crates/v/actix_failwrap
[license-badge]: https://badges.ws/crates/l/actix_failwrap
[docs-badge]: https://badges.ws/crates/docs/actix_failwrap
[downloads-badge]: https://badges.ws/crates/dt/actix_failwrap
[codecov-badge]: https://img.shields.io/codecov/c/github/FlakySL/actix_failwrap
[tests-badge]: https://github.com/FlakySL/actix_failwrap/actions/workflows/010-tests.yml/badge.svg
[discord-badge]: https://badges.ws/discord/online/1344769456731197450
[crate]: https://crates.io/crates/actix_failwrap
[license]: https://github.com/FlakySL/actix_failwrap/blob/main/LICENSE
[docs]: https://docs.rs/actix_failwrap
[downloads]: https://docs.rs/actix_failwrap
[codecov]: https://app.codecov.io/gh/FlakySL/actix_failwrap
[discord]: https://discord.gg/AJWFyps23a