axum-error-sets 0.2.0

Typed, composable HTTP error sets for Axum and Aide
Documentation
//! Typed, composable HTTP error sets for Axum and Aide.
//!
//! `axum-error-sets` provides compile-time guarantees for HTTP error handling in Axum applications.
//! Instead of using monolithic error enums or loosely-typed responses, functions declare the exact set
//! of HTTP status codes they can return using type-level tuple sets (e.g., `(NotFound, Unauthorized)`).
//!
//! ### Key Concepts & Features
//!
//! * **Powered by [`type-sets`](https://docs.rs/type-sets/):** Uses type-level set operations under the hood to manage,
//!   contain, and convert tuple sets of status codes at compile time.
//! * **No Per-Function Custom Error Enums:** Eliminates the need to construct large, domain-wide error enums or
//!   define bespoke `Error` types for every function layer.
//! * **Exact Error Contracts:** Functions declare precisely which HTTP status codes they can produce in their return signature.
//! * **Subset-to-Superset Promotion:** Error sets grow deterministically as they move up application layers
//!   via `.into_superset()`. Lower-level code remains precise without restricting higher-level callers.
//! * **Custom Response Formatting:** Implement [`IntoResponseWith`] on your central error payload type (e.g., `AppError` or `StringError`)
//!   to completely control how Axum converts error values into [`IntoResponse`](axum::response::IntoResponse) for any given status code.
//! * **Compile-Time Guarantees:** Returning an undeclared status code produces a compiler error. Callers cannot silently "forget"
//!   or shrink handled error sets without explicit conversion.
//! * **Aide & OpenAPI Integration:** Implement [`AideResponseFor`] to automatically generate precise OpenAPI metadata for every status code in an error set.
//!
//! ---
//!
//! For complete runnable code, visit the [`examples/`](https://github.com/your-org/axum-error-sets/tree/main/examples) directory on GitHub.
//!
//! ### Example 1: Basic Status Mapping
//!
//! Demonstrates converting `Result` types directly into typed HTTP error statuses using `StatusResultExt`.
//!
//! ```rust,ignore
#![doc = include_str!("../examples/01_status_mapping.rs")]
//! ```
//!
//! ---
//!
//! ### Example 2: Error Set Composition
//!
//! Demonstrates how lower-level functions with small error sets transparently expand into larger caller-level contracts using `into_superset()`.
//!
//! ```rust,ignore
#![doc = include_str!("../examples/02_error_composition.rs")]
//! ```
//!
//! ---
//!
//! ### Example 3: Axum Route Handlers & Aide OpenAPI Integration
//!
//! Demonstrates integrating error sets directly into Axum handlers to automatically generate OpenAPI metadata.
//!
//! ```rust,ignore
#![doc = include_str!("../examples/03_axum_aide.rs")]
//! ```
//!
//! ### Example 4: Axum Route Handlers & Utoipa OpenAPI Integration
//!
//! Demonstrates integrating error sets directly into Axum handlers to automatically generate OpenAPI metadata using Utoipa.
//!
//! ```rust,ignore
#![doc = include_str!("../examples/04_axum_utoipa.rs")]
//! ```

use axum_core::response::Response;
use http::StatusCode;
use type_sets::Contains;

/// Implemented for all types that can be used as `E` inside [`ApiError<_, E>`].
///
/// implemented for [`NotFound`](crate::code::NotFound),
/// [`InternalServerError`](crate::code::InternalServerError), etc.
pub trait StatusWrapper: Sized {
    /// The status code associated with type.
    const STATUS_CODE: StatusCode;

    /// The inner value type that is wrapped by this status wrapper.
    type Inner;

    /// The pure type of this status wrapper, without any inner value.
    type Pure: StatusWrapper;

    /// Convert this status wrapper into its inner value.
    fn into_inner(self) -> Self::Inner;

    /// Convert this status wrapper into an [`ErrorSet`] with the
    /// given inner value type. (`into` can be used as well)
    fn into_set<T, E>(self) -> ErrorSet<T, E>
    where
        E: Contains<Self::Pure>,
        Self::Inner: Into<T>,
    {
        ErrorSet::new(self)
    }
}

/// Should be implemented for a type to be used as `T` inside [`ApiError<T, _>`].
pub trait IntoResponseWith {
    /// Convert the value into an axum [`Response`] with the given status code.
    ///
    /// This method must make sure that the response is valid for the given status code,
    /// and that it is consistent with the OpenAPI specification generated by
    /// [`OapiResponseFor`].
    ///
    /// # Example
    /// ```rust
    /// use axum_error_sets::{IntoResponseWith};
    /// use axum::response::{IntoResponse, Response};
    /// use http::StatusCode;
    ///
    /// struct MyErrorValue(String);
    ///
    /// impl IntoResponseWith for MyErrorValue {
    ///     fn into_response_with(self, status: StatusCode) -> Response {
    ///         (status, self.0).into_response()
    ///    }
    /// }
    /// ```
    fn into_response_with(self, status: StatusCode) -> Response;
}

/// Should be implemented for a type to be used as `T` inside [`ApiError<T, _>`], for
/// usage with [`aide`].
#[cfg(feature = "aide")]
pub trait AideResponseFor: IntoResponseWith {
    /// See [`aide::OperationOutput::Inner`].
    type Inner;

    /// See [`aide::OperationOutput::inferred_responses`].
    ///
    /// # Example
    /// ```rust
    /// use axum_error_sets::{IntoResponseWith, AideResponseFor};
    /// use axum::response::{IntoResponse, Response};
    /// use http::StatusCode;
    ///
    /// struct MyErrorValue(String);
    ///
    /// impl IntoResponseWith for MyErrorValue {
    ///     fn into_response_with(self, status: StatusCode) -> Response {
    ///         (status, self.0).into_response()
    ///    }
    /// }
    ///
    /// impl AideResponseFor for MyErrorValue {
    ///     type Inner = String;
    ///
    ///     fn inferred_response_for(
    ///         _ctx: &mut aide::generate::GenContext,
    ///         _operation: &mut aide::openapi::Operation,
    ///         status: StatusCode,
    ///     ) -> aide::openapi::Response {
    ///         aide::openapi::Response {
    ///             description: format!("Error: {}", status),
    ///             ..Default::default()
    ///         }
    ///     }
    /// }
    /// ```
    fn inferred_response_for(
        ctx: &mut aide::generate::GenContext,
        operation: &mut aide::openapi::Operation,
        status: StatusCode,
    ) -> aide::openapi::Response;
}

#[cfg(feature = "utoipa")]
pub trait UtoipaResponseFor: IntoResponseWith {
    /// Generates OpenAPI response specifications for a given status code.
    fn response_for(status: StatusCode) -> utoipa::openapi::Response;
}

pub use api_error::*;
mod api_error;

#[cfg(feature = "aide")]
mod aide_impls;

#[cfg(feature = "utoipa")]
mod utoipa_impls;

pub mod code;

#[cfg(test)]
mod tests;

mod result_ext;
pub use result_ext::*;