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}