kit_rs/
error.rs

1//! Framework-wide error types
2//!
3//! Provides a unified error type that can be used throughout the framework
4//! and automatically converts to appropriate HTTP responses.
5
6use thiserror::Error;
7
8/// Trait for errors that can be converted to HTTP responses
9///
10/// Implement this trait on your domain errors to customize the HTTP status code
11/// and message that will be returned when the error is converted to a response.
12///
13/// # Example
14///
15/// ```rust,ignore
16/// use kit::HttpError;
17///
18/// #[derive(Debug)]
19/// struct UserNotFoundError { user_id: i32 }
20///
21/// impl std::fmt::Display for UserNotFoundError {
22///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23///         write!(f, "User {} not found", self.user_id)
24///     }
25/// }
26///
27/// impl std::error::Error for UserNotFoundError {}
28///
29/// impl HttpError for UserNotFoundError {
30///     fn status_code(&self) -> u16 { 404 }
31/// }
32/// ```
33pub trait HttpError: std::error::Error + Send + Sync + 'static {
34    /// HTTP status code (default: 500)
35    fn status_code(&self) -> u16 {
36        500
37    }
38
39    /// Error message for HTTP response (default: error's Display)
40    fn error_message(&self) -> String {
41        self.to_string()
42    }
43}
44
45/// Simple wrapper for creating one-off domain errors
46///
47/// Use this for inline/ad-hoc errors when you don't want to create
48/// a dedicated error type.
49///
50/// # Example
51///
52/// ```rust,ignore
53/// use kit::{AppError, FrameworkError};
54///
55/// pub async fn process() -> Result<(), FrameworkError> {
56///     if invalid {
57///         return Err(AppError::bad_request("Invalid input").into());
58///     }
59///     Ok(())
60/// }
61/// ```
62#[derive(Debug, Clone)]
63pub struct AppError {
64    message: String,
65    status_code: u16,
66}
67
68impl AppError {
69    /// Create a new AppError with status 500 (Internal Server Error)
70    pub fn new(message: impl Into<String>) -> Self {
71        Self {
72            message: message.into(),
73            status_code: 500,
74        }
75    }
76
77    /// Set the HTTP status code
78    pub fn status(mut self, code: u16) -> Self {
79        self.status_code = code;
80        self
81    }
82
83    /// Create a 404 Not Found error
84    pub fn not_found(message: impl Into<String>) -> Self {
85        Self::new(message).status(404)
86    }
87
88    /// Create a 400 Bad Request error
89    pub fn bad_request(message: impl Into<String>) -> Self {
90        Self::new(message).status(400)
91    }
92
93    /// Create a 401 Unauthorized error
94    pub fn unauthorized(message: impl Into<String>) -> Self {
95        Self::new(message).status(401)
96    }
97
98    /// Create a 403 Forbidden error
99    pub fn forbidden(message: impl Into<String>) -> Self {
100        Self::new(message).status(403)
101    }
102
103    /// Create a 422 Unprocessable Entity error
104    pub fn unprocessable(message: impl Into<String>) -> Self {
105        Self::new(message).status(422)
106    }
107
108    /// Create a 409 Conflict error
109    pub fn conflict(message: impl Into<String>) -> Self {
110        Self::new(message).status(409)
111    }
112}
113
114impl std::fmt::Display for AppError {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        write!(f, "{}", self.message)
117    }
118}
119
120impl std::error::Error for AppError {}
121
122impl HttpError for AppError {
123    fn status_code(&self) -> u16 {
124        self.status_code
125    }
126
127    fn error_message(&self) -> String {
128        self.message.clone()
129    }
130}
131
132impl From<AppError> for FrameworkError {
133    fn from(e: AppError) -> Self {
134        FrameworkError::Domain {
135            message: e.message,
136            status_code: e.status_code,
137        }
138    }
139}
140
141/// Framework-wide error type
142///
143/// This enum represents all possible errors that can occur in the framework.
144/// It implements `From<FrameworkError> for Response` so errors can be propagated
145/// using the `?` operator in controller handlers.
146///
147/// # Example
148///
149/// ```rust,ignore
150/// use kit::{App, FrameworkError, Response};
151///
152/// pub async fn index(_req: Request) -> Response {
153///     let service = App::resolve::<MyService>()?;  // Returns FrameworkError on failure
154///     // ...
155/// }
156/// ```
157///
158/// # Automatic Error Conversion
159///
160/// `FrameworkError` implements `From` for common error types, allowing seamless
161/// use of the `?` operator:
162///
163/// ```rust,ignore
164/// use kit::{DB, FrameworkError};
165/// use sea_orm::ActiveModelTrait;
166///
167/// pub async fn create_todo() -> Result<Todo, FrameworkError> {
168///     let todo = new_todo.insert(&*DB::get()?).await?;  // DbErr converts automatically!
169///     Ok(todo)
170/// }
171/// ```
172#[derive(Debug, Clone, Error)]
173pub enum FrameworkError {
174    /// Service not found in the dependency injection container
175    #[error("Service '{type_name}' not registered in container")]
176    ServiceNotFound {
177        /// The type name of the service that was not found
178        type_name: &'static str,
179    },
180
181    /// Parameter extraction failed (missing or invalid parameter)
182    #[error("Missing required parameter: {param_name}")]
183    ParamError {
184        /// The name of the parameter that failed extraction
185        param_name: String,
186    },
187
188    /// Validation error
189    #[error("Validation error for '{field}': {message}")]
190    ValidationError {
191        /// The field that failed validation
192        field: String,
193        /// The validation error message
194        message: String,
195    },
196
197    /// Database error
198    #[error("Database error: {0}")]
199    Database(String),
200
201    /// Generic internal server error
202    #[error("Internal server error: {message}")]
203    Internal {
204        /// The error message
205        message: String,
206    },
207
208    /// Domain/application error with custom status code
209    ///
210    /// Used for user-defined domain errors that need custom HTTP status codes.
211    #[error("{message}")]
212    Domain {
213        /// The error message
214        message: String,
215        /// HTTP status code
216        status_code: u16,
217    },
218}
219
220impl FrameworkError {
221    /// Create a ServiceNotFound error for a given type
222    pub fn service_not_found<T: ?Sized>() -> Self {
223        Self::ServiceNotFound {
224            type_name: std::any::type_name::<T>(),
225        }
226    }
227
228    /// Create a ParamError for a missing parameter
229    pub fn param(name: impl Into<String>) -> Self {
230        Self::ParamError {
231            param_name: name.into(),
232        }
233    }
234
235    /// Create a ValidationError
236    pub fn validation(field: impl Into<String>, message: impl Into<String>) -> Self {
237        Self::ValidationError {
238            field: field.into(),
239            message: message.into(),
240        }
241    }
242
243    /// Create a DatabaseError
244    pub fn database(message: impl Into<String>) -> Self {
245        Self::Database(message.into())
246    }
247
248    /// Create an Internal error
249    pub fn internal(message: impl Into<String>) -> Self {
250        Self::Internal {
251            message: message.into(),
252        }
253    }
254
255    /// Create a Domain error with custom status code
256    pub fn domain(message: impl Into<String>, status_code: u16) -> Self {
257        Self::Domain {
258            message: message.into(),
259            status_code,
260        }
261    }
262
263    /// Get the HTTP status code for this error
264    pub fn status_code(&self) -> u16 {
265        match self {
266            Self::ServiceNotFound { .. } => 500,
267            Self::ParamError { .. } => 400,
268            Self::ValidationError { .. } => 422,
269            Self::Database(_) => 500,
270            Self::Internal { .. } => 500,
271            Self::Domain { status_code, .. } => *status_code,
272        }
273    }
274}
275
276// Implement From<DbErr> for automatic error conversion with ?
277impl From<sea_orm::DbErr> for FrameworkError {
278    fn from(e: sea_orm::DbErr) -> Self {
279        Self::Database(e.to_string())
280    }
281}