Skip to main content

axum_error_sets/
lib.rs

1//! Typed, composable HTTP error sets for Axum and Aide.
2//!
3//! `axum-error-sets` provides compile-time guarantees for HTTP error handling in Axum applications.
4//! Instead of using monolithic error enums or loosely-typed responses, functions declare the exact set
5//! of HTTP status codes they can return using type-level tuple sets (e.g., `(NotFound, Unauthorized)`).
6//!
7//! ### Key Concepts & Features
8//!
9//! * **Powered by [`type-sets`](https://docs.rs/type-sets/):** Uses type-level set operations under the hood to manage,
10//!   contain, and convert tuple sets of status codes at compile time.
11//! * **No Per-Function Custom Error Enums:** Eliminates the need to construct large, domain-wide error enums or
12//!   define bespoke `Error` types for every function layer.
13//! * **Exact Error Contracts:** Functions declare precisely which HTTP status codes they can produce in their return signature.
14//! * **Subset-to-Superset Promotion:** Error sets grow deterministically as they move up application layers
15//!   via `.into_superset()`. Lower-level code remains precise without restricting higher-level callers.
16//! * **Custom Response Formatting:** Implement [`ErrorSetValue`] on your central error payload type (e.g., `AppError` or `StringError`)
17//!   to completely control how Axum converts error values into [`IntoResponse`](axum::response::IntoResponse) for any given status code.
18//! * **Compile-Time Guarantees:** Returning an undeclared status code produces a compiler error. Callers cannot silently "forget"
19//!   or shrink handled error sets without explicit conversion.
20//! * **Aide & OpenAPI Integration:** Implement [`AideErrorSetValue`] to automatically generate precise OpenAPI metadata for every status code in an error set.
21//!
22//! ---
23//!
24//! For complete runnable code, visit the [`examples/`](https://github.com/your-org/axum-error-sets/tree/main/examples) directory on GitHub.
25//!
26//! ### Example 1: Basic Status Mapping
27//!
28//! Demonstrates converting `Result` types directly into typed HTTP error statuses using `StatusResultExt`.
29//!
30//! ```rust,ignore
31#![doc = include_str!("../examples/01_status_mapping.rs")]
32//! ```
33//!
34//! ---
35//!
36//! ### Example 2: Error Set Composition
37//!
38//! Demonstrates how lower-level functions with small error sets transparently expand into larger caller-level contracts using `into_superset()`.
39//!
40//! ```rust,ignore
41#![doc = include_str!("../examples/02_error_composition.rs")]
42//! ```
43//!
44//! ---
45//!
46//! ### Example 3: Axum Route Handlers & Aide OpenAPI Integration
47//!
48//! Demonstrates integrating error sets directly into Axum handlers to automatically generate OpenAPI metadata.
49//!
50//! ```rust,ignore
51#![doc = include_str!("../examples/03_axum_aide.rs")]
52//! ```
53
54use axum_core::response::Response;
55use http::StatusCode;
56use type_sets::Contains;
57
58/// Implemented for all types that can be used as `E` inside [`ApiError<_, E>`].
59///
60/// implemented for [`NotFound`](crate::code::NotFound),
61/// [`InternalServerError`](crate::code::InternalServerError), etc.
62pub trait StatusWrapper: Sized {
63    /// The status code associated with type.
64    const STATUS_CODE: StatusCode;
65
66    /// The inner value type that is wrapped by this status wrapper.
67    type Inner;
68
69    /// The pure type of this status wrapper, without any inner value.
70    type Pure: StatusWrapper;
71
72    /// Convert this status wrapper into its inner value.
73    fn into_inner(self) -> Self::Inner;
74
75    /// Convert this status wrapper into an [`ErrorSet`] with the
76    /// given inner value type. (`into` can be used as well)
77    fn into_set<T, E>(self) -> ErrorSet<T, E>
78    where
79        E: Contains<Self::Pure>,
80        Self::Inner: Into<T>,
81    {
82        ErrorSet::new(self)
83    }
84}
85
86/// Should be implemented for a type to be used as `T` inside [`ApiError<T, _>`].
87pub trait ErrorSetValue {
88    /// Convert the value into an axum [`Response`] with the given status code.
89    ///
90    /// This method must make sure that the response is valid for the given status code,
91    /// and that it is consistent with the OpenAPI specification generated by
92    /// [`OapiResponseFor`].
93    ///
94    /// # Example
95    /// ```rust
96    /// use axum_error_sets::{ErrorSetValue};
97    /// use axum::response::{IntoResponse, Response};
98    /// use http::StatusCode;
99    ///
100    /// struct MyErrorValue(String);
101    ///
102    /// impl ErrorSetValue for MyErrorValue {
103    ///     fn into_response_with(self, status: StatusCode) -> Response {
104    ///         (status, self.0).into_response()
105    ///    }
106    /// }
107    /// ```
108    fn into_response_with(self, status: StatusCode) -> Response;
109}
110
111/// Should be implemented for a type to be used as `T` inside [`ApiError<T, _>`], for
112/// usage with [`aide`].
113#[cfg(feature = "aide")]
114pub trait AideErrorSetValue: ErrorSetValue {
115    /// See [`aide::OperationOutput::Inner`].
116    type Inner;
117
118    /// See [`aide::OperationOutput::inferred_responses`].
119    ///
120    /// # Example
121    /// ```rust
122    /// use axum_error_sets::{ErrorSetValue, AideErrorSetValue};
123    /// use axum::response::{IntoResponse, Response};
124    /// use http::StatusCode;
125    ///
126    /// struct MyErrorValue(String);
127    ///
128    /// impl ErrorSetValue for MyErrorValue {
129    ///     fn into_response_with(self, status: StatusCode) -> Response {
130    ///         (status, self.0).into_response()
131    ///    }
132    /// }
133    ///
134    /// impl AideErrorSetValue for MyErrorValue {
135    ///     type Inner = String;
136    ///
137    ///     fn inferred_response_for(
138    ///         _ctx: &mut aide::generate::GenContext,
139    ///         _operation: &mut aide::openapi::Operation,
140    ///         status: StatusCode,
141    ///     ) -> aide::openapi::Response {
142    ///         aide::openapi::Response {
143    ///             description: format!("Error: {}", status),
144    ///             ..Default::default()
145    ///         }
146    ///     }
147    /// }
148    /// ```
149    fn inferred_response_for(
150        ctx: &mut aide::generate::GenContext,
151        operation: &mut aide::openapi::Operation,
152        status: StatusCode,
153    ) -> aide::openapi::Response;
154}
155
156pub use api_error::*;
157mod api_error;
158
159#[cfg(feature = "aide")]
160mod aide_impls;
161
162pub mod code;
163
164#[cfg(test)]
165mod tests;
166
167mod result_ext;
168pub use result_ext::*;