axum_err_handler_ctx/lib.rs
1//! # Error Response Context
2//!
3//! This module provides types and traits for creating standardized error response contexts
4//! that can be used with custom error response functions in the Axum Error Handler crate.
5//!
6//! The main purpose is to allow custom error response functions to receive structured
7//! error information (status code, error code, message) that can be used to generate
8//! custom response formats while maintaining consistency with the error handling system.
9//!
10//! ## Example Usage
11//!
12//! ```rust
13//! use axum_error_handler::ErrorResponseContext;
14//! use axum::response::Response;
15//!
16//! fn custom_error_handler(ctx: ErrorResponseContext) -> Response {
17//! // Access error information
18//! let status = ctx.status_code().unwrap_or(500);
19//! let code = ctx.code().unwrap_or(&"UNKNOWN".to_string());
20//! let message = ctx.message().unwrap_or(&"Error occurred".to_string());
21//!
22//! // Create custom response format
23//! Response::builder()
24//! .status(status)
25//! .body(format!("Error {}: {}", code, message).into())
26//! .unwrap()
27//! }
28//! ```
29
30use axum::response::IntoResponse;
31
32/// A trait for converting error types into structured error response contexts.
33///
34/// This trait allows error types to be converted into a standardized context
35/// that contains status code, error code, and message information. This context
36/// can then be used by custom error response functions to generate appropriate
37/// HTTP responses.
38///
39/// # Example Implementation
40///
41/// ```rust
42/// use axum_error_handler::{IntoErrorResponseContext, ErrorResponseContext};
43///
44/// struct MyError {
45/// message: String,
46/// }
47///
48/// impl IntoErrorResponseContext for MyError {
49/// fn into_response_context(self) -> ErrorResponseContext {
50/// ErrorResponseContext::builder()
51/// .status_code(400)
52/// .code("MY_ERROR".to_string())
53/// .message(self.message)
54/// .build()
55/// }
56/// }
57/// ```
58pub trait IntoErrorResponseContext {
59 /// Converts the error into an `ErrorResponseContext`.
60 fn into_response_context(self) -> ErrorResponseContext;
61}
62
63/// A structured context containing error information for generating HTTP responses.
64///
65/// This struct holds the essential components of an error response:
66/// - HTTP status code
67/// - Error code (for API consumers)
68/// - Human-readable error message
69///
70/// It can be used by custom error response functions to access error details
71/// and generate appropriate responses in any desired format.
72///
73/// # Example
74///
75/// ```rust
76/// use axum_error_handler::ErrorResponseContext;
77///
78/// let context = ErrorResponseContext::builder()
79/// .status_code(404)
80/// .code("NOT_FOUND".to_string())
81/// .message("Resource not found".to_string())
82/// .build();
83///
84/// assert_eq!(context.status_code(), Some(404));
85/// assert_eq!(context.code(), Some(&"NOT_FOUND".to_string()));
86/// ```
87#[derive(Clone)]
88pub struct ErrorResponseContext {
89 status_code: Option<u16>,
90 code: Option<String>,
91 message: Option<String>,
92}
93
94impl ErrorResponseContext {
95 /// Creates a new builder for constructing an `ErrorResponseContext`.
96 ///
97 /// This is the recommended way to create a new context with specific values.
98 ///
99 /// # Example
100 ///
101 /// ```rust
102 /// use axum_error_handler::ErrorResponseContext;
103 ///
104 /// let context = ErrorResponseContext::builder()
105 /// .status_code(400)
106 /// .code("VALIDATION_ERROR".to_string())
107 /// .message("Invalid input provided".to_string())
108 /// .build();
109 /// ```
110 pub fn builder() -> ErrorResponseBuilder {
111 ErrorResponseBuilder::new()
112 }
113
114 /// Creates a new empty `ErrorResponseContext`.
115 ///
116 /// All fields will be `None` initially. Use the builder pattern or
117 /// the setter methods to populate the context.
118 pub fn new() -> Self {
119 Self {
120 status_code: None,
121 code: None,
122 message: None,
123 }
124 }
125
126 /// Sets the HTTP status code for this error context.
127 ///
128 /// # Arguments
129 ///
130 /// * `status_code` - The HTTP status code (e.g., 400, 404, 500)
131 pub(crate) fn set_status_code(&mut self, status_code: u16) {
132 self.status_code = Some(status_code);
133 }
134
135 /// Sets the error code for this error context.
136 ///
137 /// The error code is typically a machine-readable identifier
138 /// that API consumers can use to handle specific error types.
139 ///
140 /// # Arguments
141 ///
142 /// * `code` - A string identifier for the error type (e.g., "VALIDATION_ERROR")
143 pub(crate) fn set_code(&mut self, code: String) {
144 self.code = Some(code);
145 }
146
147 /// Sets the error message for this error context.
148 ///
149 /// The message should be human-readable and provide details
150 /// about what went wrong.
151 ///
152 /// # Arguments
153 ///
154 /// * `message` - A descriptive error message
155 pub(crate) fn set_message(&mut self, message: String) {
156 self.message = Some(message);
157 }
158
159 /// Returns the HTTP status code if set.
160 ///
161 /// # Returns
162 ///
163 /// `Some(status_code)` if a status code was set, `None` otherwise.
164 pub fn status_code(&self) -> Option<u16> {
165 self.status_code
166 }
167
168 /// Returns a reference to the error code if set.
169 ///
170 /// # Returns
171 ///
172 /// `Some(&code)` if an error code was set, `None` otherwise.
173 pub fn code(&self) -> Option<&String> {
174 self.code.as_ref()
175 }
176
177 /// Returns a reference to the error message if set.
178 ///
179 /// # Returns
180 ///
181 /// `Some(&message)` if a message was set, `None` otherwise.
182 pub fn message(&self) -> Option<&String> {
183 self.message.as_ref()
184 }
185}
186
187/// A builder for constructing `ErrorResponseContext` instances.
188///
189/// This builder provides a fluent interface for setting error context properties
190/// and ensures that contexts are created in a consistent manner.
191///
192/// # Example
193///
194/// ```rust
195/// use axum_error_handler::ErrorResponseContext;
196///
197/// let context = ErrorResponseContext::builder()
198/// .status_code(422)
199/// .code("VALIDATION_FAILED".to_string())
200/// .message("The provided data failed validation".to_string())
201/// .build();
202/// ```
203pub struct ErrorResponseBuilder {
204 code: Option<String>,
205 message: Option<String>,
206 status_code: Option<u16>,
207}
208
209impl ErrorResponseBuilder {
210 /// Creates a new empty builder.
211 pub fn new() -> Self {
212 Self {
213 code: None,
214 message: None,
215 status_code: None,
216 }
217 }
218
219 /// Sets the error code for the context being built.
220 ///
221 /// # Arguments
222 ///
223 /// * `code` - A machine-readable error identifier
224 ///
225 /// # Returns
226 ///
227 /// The builder instance for method chaining.
228 pub fn code(mut self, code: String) -> Self {
229 self.code = Some(code);
230 self
231 }
232
233 /// Sets the error message for the context being built.
234 ///
235 /// # Arguments
236 ///
237 /// * `message` - A human-readable error description
238 ///
239 /// # Returns
240 ///
241 /// The builder instance for method chaining.
242 pub fn message(mut self, message: String) -> Self {
243 self.message = Some(message);
244 self
245 }
246
247 /// Sets the HTTP status code for the context being built.
248 ///
249 /// # Arguments
250 ///
251 /// * `status_code` - The HTTP status code (e.g., 400, 404, 500)
252 ///
253 /// # Returns
254 ///
255 /// The builder instance for method chaining.
256 pub fn status_code(mut self, status_code: u16) -> Self {
257 self.status_code = Some(status_code);
258 self
259 }
260
261 /// Builds the final `ErrorResponseContext` with the configured values.
262 ///
263 /// # Returns
264 ///
265 /// A new `ErrorResponseContext` instance with the values set on this builder.
266 pub fn build(self) -> ErrorResponseContext {
267 let mut context = ErrorResponseContext::new();
268 if let Some(code) = self.code {
269 context.set_code(code);
270 }
271 if let Some(message) = self.message {
272 context.set_message(message);
273 }
274 if let Some(status_code) = self.status_code {
275 context.set_status_code(status_code);
276 }
277 context
278 }
279}
280
281/// Default implementation for converting an `ErrorResponseContext` into an Axum HTTP response.
282///
283/// This implementation creates a standardized JSON response with the following format:
284///
285/// ```json
286/// {
287/// "result": null,
288/// "error": {
289/// "code": "ERROR_CODE",
290/// "message": "Error description"
291/// }
292/// }
293/// ```
294///
295/// # Defaults
296///
297/// - Status code: 500 (Internal Server Error) if not specified
298/// - Error code: "UNKNOWN_ERROR" if not specified
299/// - Message: "An error occurred" if not specified
300///
301/// # Example
302///
303/// ```rust
304/// use axum::response::IntoResponse;
305/// use axum_error_handler::ErrorResponseContext;
306///
307/// let context = ErrorResponseContext::builder()
308/// .status_code(404)
309/// .code("NOT_FOUND".to_string())
310/// .message("The requested resource was not found".to_string())
311/// .build();
312///
313/// let response = context.into_response();
314/// // Creates a 404 response with JSON body
315/// ```
316impl IntoResponse for ErrorResponseContext {
317 fn into_response(self) -> axum::response::Response {
318 let status_code = self.status_code.unwrap_or(500);
319 let code = self.code.unwrap_or_else(|| "UNKNOWN_ERROR".to_string());
320 let message = self
321 .message
322 .unwrap_or_else(|| "An error occurred".to_string());
323
324 let body = axum::Json(serde_json::json!({
325 "result": null,
326 "error": {
327 "code": code,
328 "message": message,
329 }
330 }));
331
332 axum::http::Response::builder()
333 .status(status_code)
334 .header("content-type", "application/json")
335 .body(body.into_response().into_body())
336 .unwrap()
337 }
338}