#axum #error #error-handling

axum-route-error

A common error response for Axum servers

8 releases (4 major breaking)

5.0.1 Dec 29, 2023
5.0.0 Nov 28, 2023
4.1.0 Oct 29, 2023
3.0.0 Sep 6, 2023
1.0.0 Jun 30, 2023

#233 in HTTP server

Download history 301/week @ 2023-11-01 203/week @ 2023-11-08 169/week @ 2023-11-15 129/week @ 2023-11-22 164/week @ 2023-11-29 106/week @ 2023-12-06 130/week @ 2023-12-13 81/week @ 2023-12-20 176/week @ 2023-12-27 201/week @ 2024-01-03 183/week @ 2024-01-10 156/week @ 2024-01-17 201/week @ 2024-01-24 116/week @ 2024-01-31 326/week @ 2024-02-07 244/week @ 2024-02-14

913 downloads per month

MIT license

18KB
266 lines

Axum Route Error

A common error response for Axum routes

crate docs


This exists to encapsulate a number of common patterns when returning errors from Axum routes. These patterns include:

  • wanting to return error responses as a JSON object
  • to automatically convert errors raised in the route hander into that standardised JSON response
  • provide appropriate default error messages for public use, which takes into account the status code in use
  • a need to optionally add extra data to the error JSON output

A basic tutorial

Below is a mock example route to download a User object:

use ::axum::extract::State;
use ::axum_route_error::RouteError;
use ::sea_orm::DatabaseConnection;

pub async fn route_get_user(
    State(ref db): State<DatabaseConnection>,
    Path(username): Path<String>
) -> Result<ExampleUser, RouteError> {
    // If this errors, then a `RouteError::new_internal_server()` is returned.
    let user = get_user_from_database(db, &username).await?;

    Ok(user)
}

If the get_user_from_database function returns an error, then the handler will return a Response. The Response will have a 500 status code (for an internal error), and return the following output:

{
  "error": "An unexpected error occurred"
}

Different RouteError types

Let's presume get_user_from_database returns a Result<Option, Error>. We want to return a 500 if it returns an error (like above), and change the code to return a 404 if the user is not found.

use ::axum::extract::State;
use ::axum_route_error::RouteError;
use ::sea_orm::DatabaseConnection;

pub async fn route_get_user(
    State(ref db): State<DatabaseConnection>,
    Path(username): Path<String>
) -> Result<ExampleUser, RouteError> {
    let user = get_user_from_database(db, &username).await?
      // This additional line will return a 404 if the user is not found.
      .ok_or_else(|| RouteError::new_not_found())?;

    Ok(user)
}

If the user is not found (get_user_from_database returns None), then this will return a 404 Response with the following JSON:

{
  "error": "The resource was not found"
}

Adding additional error data

Next let's add extra information to the error. Something more than just an error message.

This can be done by making a new type that serializes using Serde, and then adding this to the RouteError.

use ::axum::extract::State;
use ::axum_route_error::RouteError;
use ::sea_orm::DatabaseConnection;
use ::serde::Deserialize;
use ::serde::Serialize;

// The additional error information needs to derive these three traits.
#[derive(Deserialize, Serialize, Debug)]
pub struct UserErrorInformation {
  pub username: String
}

pub async fn route_get_user(
    State(ref db): State<DatabaseConnection>,
    Path(username): Path<String>
// The `RouteError` needs the additional data marked here
) -> Result<ExampleUser, RouteError<UserErrorInformation>> {
    let user = get_user_from_database(db, &username).await?
      .ok_or_else(move || {
        // Then you can add the data through method chaining
        RouteError::new_not_found()
          .set_error_data(UserErrorInformation {
            username,
          })
      })?;

    Ok(user)
}

If the user is not found (get_user_from_database returns None), then this will return a 404 Response with the following JSON:

{
  "error": "The resource was not found",
  "username": "<the-username>"
}

Making Internal Errors public

Sometimes you want to make internal errors public, such as for internal services.

For this you can use the RouteInternalError. It's identical, but adds adds internal_error information to the response.

Dependencies

~7–10MB
~183K SLoC