2 unstable releases

0.1.0 Dec 27, 2023
0.0.0 Dec 27, 2023

#7 in #http-status

MIT license

14KB
162 lines

Axum-dyn-error

Dynamic error handling for Axum HTTP handlers

[dependencies]
axum-dyn-error = "0.1"

Only supports axum v0.6

This crate provides foundational logic for creating and handling dynamic HTTP errors.

Implementing the HttpError trait on errors allows you to customize how the errors and displayed in error responses.

I recommend using thiserror for defining your user facing error types.

In order to use the dynamic error handling you should replace your [Result] return types with the [HttpResult] type from axum_dyn_error.


use axum_dyn_error::{HttpResult, HttpError, StatusCode};
use thiserror::Error;
use axum::{extract::Path, Json};

#[derive(Debug, Error)]
pub enum ExampleError {
    #[error("User not found")]
    MissingUser,
    #[error("Username was invalid")]
    InvalidUsername
}

impl HttpError for ExampleError {

    /// Customize the HTTP status code
    fn status(&self) -> StatusCode {
        match self {
            ExampleError::MissingUser => StatusCode::NOT_FOUND,
            ExampleError::InvalidUsername => StatusCode::BAD_REQUEST
        }
    }
}

/// Dummy structure representing a user
pub struct User;

/// Mock function for finding a user by id
pub async fn get_user_by_id(user_id: u32) -> Option<User> { unimplemented!() }

/// Example handler
pub async fn example_handler(
    Path(user_id): Path<u32>
) -> HttpResult<Json<User>> {
    let user = get_user_by_id(user_id)
        .await
        .ok_or(ExampleError::MissingUser)?;

    Ok(Json(user))
}

Anyhow support

Axum-dyn-error supports anyhow errors through the anyhow feature flag, by default the hide-anyhow feature flag is enabled which prevents the anyhow error message from being included in the error response instead responding with "Server error".


use axum_dyn_error::{HttpResult, HttpError, StatusCode};
use axum::{extract::Path, Json};
use anyhow::anyhow;

/// Dummy structure representing a user
pub struct User;

/// Mock function for finding a user by id
pub async fn get_user_by_id(user_id: u32) -> Option<User> { unimplemented!() }

/// Example handler
pub async fn example_handler(
    Path(user_id): Path<u32>
) -> HttpResult<Json<User>> {
    let user = get_user_by_id(user_id)
        .await
        .ok_or(anyhow!("Missing user"))?;

    Ok(Json(user))
}

Using [AnyhowStatusExt] the anyhow error types can have an HTTP status code associated with them, by default anyhow errors just use "500 Internal server error":


use axum_dyn_error::{HttpResult, HttpError, StatusCode, anyhow::AnyhowStatusExt};
use axum::{extract::Path, Json};
use anyhow::anyhow;

/// Dummy structure representing a user
pub struct User;

/// Mock function for finding a user by id
pub async fn get_user_by_id(user_id: u32) -> Option<User> { unimplemented!() }

/// Example handler
pub async fn example_handler(
    Path(user_id): Path<u32>
) -> HttpResult<Json<User>> {
    let user = get_user_by_id(user_id)
        .await
        .ok_or(
            anyhow!("Missing user")
                .status(StatusCode::NOT_FOUND)
        )?;

    Ok(Json(user))
}

Custom response

By default the responses generated from the errors use the "reason" as a text response body. You can change this by create a structure and implementing IntoHttpErrorResponse on that structure:


use axum_dyn_error::{HttpResult, HttpError, IntoHttpErrorResponse};
use axum::response::{Response, IntoResponse};

pub struct CustomErrorResponse;

impl IntoHttpErrorResponse for CustomErrorResponse {
    fn into_response(error: Box<dyn HttpError>) -> Response {
        // Your logic to create the response from the error example:
        (error.status(), error.reason()).into_response()
    }
}

// You can then alias the HttpResult type
pub type MyHttpResult<T> = HttpResult<T, CustomErrorResponse>;

Crate Features

The default features are ["log", "hide-anyhow"]

Feature Description
log Logs errors that are created using log::error!
anyhow Adds support for handling anyhow error types
hide-anyhow Replaces anyhow error messages in HTTP responses with a generic server error message

Dependencies

~1.6–2.4MB
~48K SLoC