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