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 [`IntoResponseWith`] 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 [`AideResponseFor`] 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//!
54//! ### Example 4: Axum Route Handlers & Utoipa OpenAPI Integration
55//!
56//! Demonstrates integrating error sets directly into Axum handlers to automatically generate OpenAPI metadata using Utoipa.
57//!
58//! ```rust,ignore
59#![doc = include_str!("../examples/04_axum_utoipa.rs")]
60//! ```
61
62use axum_core::response::Response;
63use http::StatusCode;
64use type_sets::Contains;
65
66/// Implemented for all types that can be used as `E` inside [`ApiError<_, E>`].
67///
68/// implemented for [`NotFound`](crate::code::NotFound),
69/// [`InternalServerError`](crate::code::InternalServerError), etc.
70pub trait StatusWrapper: Sized {
71    /// The status code associated with type.
72    const STATUS_CODE: StatusCode;
73
74    /// The inner value type that is wrapped by this status wrapper.
75    type Inner;
76
77    /// The pure type of this status wrapper, without any inner value.
78    type Pure: StatusWrapper;
79
80    /// Convert this status wrapper into its inner value.
81    fn into_inner(self) -> Self::Inner;
82
83    /// Convert this status wrapper into an [`ErrorSet`] with the
84    /// given inner value type. (`into` can be used as well)
85    fn into_set<T, E>(self) -> ErrorSet<T, E>
86    where
87        E: Contains<Self::Pure>,
88        Self::Inner: Into<T>,
89    {
90        ErrorSet::new(self)
91    }
92}
93
94/// Should be implemented for a type to be used as `T` inside [`ApiError<T, _>`].
95pub trait IntoResponseWith {
96    /// Convert the value into an axum [`Response`] with the given status code.
97    ///
98    /// This method must make sure that the response is valid for the given status code,
99    /// and that it is consistent with the OpenAPI specification generated by
100    /// [`OapiResponseFor`].
101    ///
102    /// # Example
103    /// ```rust
104    /// use axum_error_sets::{IntoResponseWith};
105    /// use axum::response::{IntoResponse, Response};
106    /// use http::StatusCode;
107    ///
108    /// struct MyErrorValue(String);
109    ///
110    /// impl IntoResponseWith for MyErrorValue {
111    ///     fn into_response_with(self, status: StatusCode) -> Response {
112    ///         (status, self.0).into_response()
113    ///    }
114    /// }
115    /// ```
116    fn into_response_with(self, status: StatusCode) -> Response;
117}
118
119/// Should be implemented for a type to be used as `T` inside [`ApiError<T, _>`], for
120/// usage with [`aide`].
121#[cfg(feature = "aide")]
122pub trait AideResponseFor: IntoResponseWith {
123    /// See [`aide::OperationOutput::Inner`].
124    type Inner;
125
126    /// See [`aide::OperationOutput::inferred_responses`].
127    ///
128    /// # Example
129    /// ```rust
130    /// use axum_error_sets::{IntoResponseWith, AideResponseFor};
131    /// use axum::response::{IntoResponse, Response};
132    /// use http::StatusCode;
133    ///
134    /// struct MyErrorValue(String);
135    ///
136    /// impl IntoResponseWith for MyErrorValue {
137    ///     fn into_response_with(self, status: StatusCode) -> Response {
138    ///         (status, self.0).into_response()
139    ///    }
140    /// }
141    ///
142    /// impl AideResponseFor for MyErrorValue {
143    ///     type Inner = String;
144    ///
145    ///     fn inferred_response_for(
146    ///         _ctx: &mut aide::generate::GenContext,
147    ///         _operation: &mut aide::openapi::Operation,
148    ///         status: StatusCode,
149    ///     ) -> aide::openapi::Response {
150    ///         aide::openapi::Response {
151    ///             description: format!("Error: {}", status),
152    ///             ..Default::default()
153    ///         }
154    ///     }
155    /// }
156    /// ```
157    fn inferred_response_for(
158        ctx: &mut aide::generate::GenContext,
159        operation: &mut aide::openapi::Operation,
160        status: StatusCode,
161    ) -> aide::openapi::Response;
162}
163
164#[cfg(feature = "utoipa")]
165pub trait UtoipaResponseFor: IntoResponseWith {
166    /// Generates OpenAPI response specifications for a given status code.
167    fn response_for(status: StatusCode) -> utoipa::openapi::Response;
168}
169
170pub use api_error::*;
171mod api_error;
172
173#[cfg(feature = "aide")]
174mod aide_impls;
175
176#[cfg(feature = "utoipa")]
177mod utoipa_impls;
178
179pub mod code;
180
181#[cfg(test)]
182mod tests;
183
184mod result_ext;
185pub use result_ext::*;