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::*;