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}