axum-error-sets 0.4.1

Typed, composable HTTP error sets for Axum and Aide
Documentation

axum-error-sets

Crates.io Documentation License: MIT License: Apache 2.0

Typed, composable HTTP error sets for axum, with OpenAPI generation through aide.

Instead of one large error enum per application, each function lists the exact HTTP status codes it can return, as a tuple in its return type, such as ApiResult<T, (Unauthorized, NotFound<String>)>. The sets are built on type-sets.

Features

  • Exact error contracts: each function declares which status codes it can return. Returning any other code is a compile error.
  • Every status code: there is a wrapper type for every 4xx and 5xx code, with any IntoResponse type as the body: NotFound, NotFound<String>, NotFound<Json<MyError>>.
  • ? just works: status codes convert into any error set that contains them, and small sets grow into larger ones with .into_superset().
  • Error wrapping helpers: .with_status::<BadRequest>(), .into_status::<..>(), .change_status::<..>() and .map_status(..) on any Result.
  • OpenAPI support: with the aide feature, every status code in a handler's set is documented in the generated OpenAPI spec.

Installation

[dependencies]
axum-error-sets = "0.4"

# For OpenAPI generation with aide:
axum-error-sets = { version = "0.4", features = ["aide"] }

Example

use axum::Json;
use axum_error_sets::{
    ApiResult, ApiResultExt as _, ResultStatusExt as _,
    codes::{Internal, NotFound, Unauthorized},
};

fn check_token(token: &str) -> Result<(), Unauthorized> {
    if token.is_empty() {
        return Err(Unauthorized(()));
    }
    Ok(())
}

fn find_user(id: u32) -> ApiResult<String, (NotFound<String>,)> {
    let name = lookup(id)
        .ok_or("no such user")
        .into_status::<NotFound, String>()?; // `&str` error -> `NotFound<String>`
    Ok(name)
}

async fn get_user(
    token: String,
    id: u32,
) -> ApiResult<Json<String>, (Unauthorized, NotFound<String>, Internal<String>)> {
    check_token(&token)?;                        // `Unauthorized` is in the set
    let name = find_user(id).into_superset()?;   // `(NotFound<String>,)` is a subset
    let name = normalize(name).with_status::<Internal>()?; // `String` error -> `Internal<String>`
    Ok(Json(name))
}

The examples directory has runnable examples, including handlers, aide integration, and use with axum-typed-routing.

Pairs well with axum-typed-routing

axum-typed-routing lets you declare a route's path and parameters next to its handler, checked at compile time. Combined with its api_route macro, a handler's error set appears in the OpenAPI documentation with no extra annotations:

#[api_route(GET "/item/{id}")]
async fn get_item(id: u32) -> ApiResult<Json<Item>, (Unauthorized, NotFound<String>)> {
    // ...
}

See examples/5_typed_routing.rs.

License

Licensed under either of MIT or Apache-2.0, at your option.