bottle_orm/errors.rs
1//! # Error Handling Module
2//!
3//! This module defines the error types used throughout Bottle ORM.
4//! It provides a centralized error handling system that wraps various error
5//! scenarios that can occur during database operations.
6//!
7//! ## Error Types
8//!
9//! - **InvalidData**: Data validation errors (e.g., invalid format, constraint violations)
10//! - **DatabaseError**: Wrapped sqlx errors (connection issues, query failures, etc.)
11//! - **InvalidArgument**: Invalid arguments passed to ORM methods
12//!
13//! ## Example Usage
14//!
15//! ```rust,ignore
16//! use bottle_orm::Error;
17//!
18//! async fn create_user(db: &Database, age: i32) -> Result<User, Error> {
19//! if age < 0 {
20//! return Err(Error::InvalidData("Age cannot be negative".to_string()));
21//! }
22//!
23//! let user = User { age, /* ... */ };
24//! db.model::<User>().insert(&user).await?;
25//! Ok(user)
26//! }
27//!
28//! // Error handling
29//! match create_user(&db, -5).await {
30//! Ok(user) => println!("Created: {:?}", user),
31//! Err(Error::InvalidData(msg)) => eprintln!("Validation error: {}", msg),
32//! Err(Error::DatabaseError(e)) => eprintln!("Database error: {}", e),
33//! Err(e) => eprintln!("Other error: {}", e),
34//! }
35//! ```
36
37// ============================================================================
38// External Crate Imports
39// ============================================================================
40
41use thiserror::Error;
42
43// ============================================================================
44// Error Enum Definition
45// ============================================================================
46
47/// The main error type for Bottle ORM operations.
48///
49/// This enum represents all possible errors that can occur during ORM operations.
50/// It uses the `thiserror` crate to automatically implement `std::error::Error`
51/// and provide helpful error messages.
52///
53/// # Variants
54///
55/// * `InvalidData` - Data validation errors
56/// * `DatabaseError` - Wrapped sqlx database errors
57/// * `InvalidArgument` - Invalid arguments passed to methods
58///
59/// # Display Format
60///
61/// Each variant has a custom display format defined via the `#[error(...)]` attribute:
62///
63/// - `InvalidData`: "Invalid Data {message}: {message}"
64/// - `DatabaseError`: "Database error {inner_error}:"
65/// - `InvalidArgument`: "Invalid argument {message}: {message}"
66///
67/// # Example
68///
69/// ```rust,ignore
70/// use bottle_orm::Error;
71///
72/// fn validate_age(age: i32) -> Result<(), Error> {
73/// if age < 0 {
74/// return Err(Error::InvalidData("Age must be non-negative".to_string()));
75/// }
76/// if age > 150 {
77/// return Err(Error::InvalidData("Age seems unrealistic".to_string()));
78/// }
79/// Ok(())
80/// }
81/// ```
82#[derive(Error, Debug)]
83pub enum Error {
84 /// Invalid data error.
85 ///
86 /// This variant is used when data validation fails before or after
87 /// a database operation. It typically indicates business logic violations
88 /// rather than database-level constraints.
89 ///
90 /// # When to Use
91 ///
92 /// - Data format validation (e.g., email format, phone number)
93 /// - Business rule violations (e.g., age limits, quantity constraints)
94 /// - Type conversion failures
95 /// - Serialization/deserialization errors
96 ///
97 /// # Example
98 ///
99 /// ```rust,ignore
100 /// fn validate_email(email: &str) -> Result<(), Error> {
101 /// if !email.contains('@') {
102 /// return Err(Error::InvalidData(
103 /// format!("Invalid email format: {}", email)
104 /// ));
105 /// }
106 /// Ok(())
107 /// }
108 /// ```
109 #[error("Invalid Data {0}: {0}")]
110 InvalidData(String),
111
112 /// Type conversion error.
113 ///
114 /// This variant is used when converting between Rust types and SQL types fails.
115 /// It typically occurs during value binding or deserialization.
116 ///
117 /// # When to Use
118 ///
119 /// - Failed to parse string to DateTime, UUID, or numeric types
120 /// - Type mismatch during value binding
121 /// - Format conversion errors
122 ///
123 /// # Example
124 ///
125 /// ```rust,ignore
126 /// fn parse_datetime(value: &str) -> Result<DateTime<Utc>, Error> {
127 /// value.parse::<DateTime<Utc>>()
128 /// .map_err(|e| Error::Conversion(format!("Failed to parse DateTime: {}", e)))
129 /// }
130 /// ```
131 #[error("Type conversion error: {0}")]
132 Conversion(String),
133
134 /// Database operation error.
135 ///
136 /// This variant wraps errors from the underlying sqlx library.
137 /// It's automatically converted from `sqlx::Error` via the `#[from]` attribute,
138 /// making error propagation seamless with the `?` operator.
139 ///
140 /// # Common Causes
141 ///
142 /// - **Connection Errors**: Failed to connect to database, connection pool exhausted
143 /// - **Query Errors**: SQL syntax errors, table/column not found
144 /// - **Constraint Violations**: Primary key, foreign key, unique, not null violations
145 /// - **Type Errors**: Type mismatch between Rust and SQL types
146 /// - **Row Not Found**: `fetch_one()` or `first()` found no matching rows
147 ///
148 /// # Example
149 ///
150 /// ```rust,ignore
151 /// async fn get_user_by_id(db: &Database, id: i32) -> Result<User, Error> {
152 /// // sqlx::Error is automatically converted to Error::DatabaseError
153 /// let user = db.model::<User>()
154 /// .filter("id", "=", id)
155 /// .first()
156 /// .await?;
157 /// Ok(user)
158 /// }
159 ///
160 /// // Handling specific database errors
161 /// match get_user_by_id(&db, 999).await {
162 /// Ok(user) => println!("Found: {:?}", user),
163 /// Err(Error::DatabaseError(e)) => {
164 /// if matches!(e, sqlx::Error::RowNotFound) {
165 /// eprintln!("User not found");
166 /// } else {
167 /// eprintln!("Database error: {}", e);
168 /// }
169 /// }
170 /// Err(e) => eprintln!("Other error: {}", e),
171 /// }
172 /// ```
173 #[error("Database error {0}:")]
174 DatabaseError(#[from] sqlx::Error),
175
176 /// Invalid argument error.
177 ///
178 /// This variant is used when method arguments fail validation.
179 /// It indicates programmer error (passing invalid parameters) rather than
180 /// runtime data issues.
181 ///
182 /// # When to Use
183 ///
184 /// - Negative values where only positive are allowed
185 /// - Out-of-range parameters (e.g., page number, limit)
186 /// - Invalid enum values or flags
187 /// - Null/empty values where required
188 ///
189 /// # Example
190 ///
191 /// ```rust,ignore
192 /// impl QueryBuilder {
193 /// pub fn pagination(
194 /// mut self,
195 /// max_value: usize,
196 /// default: usize,
197 /// page: usize,
198 /// value: isize,
199 /// ) -> Result<Self, Error> {
200 /// // Validate argument
201 /// if value < 0 {
202 /// return Err(Error::InvalidArgument(
203 /// "value cannot be negative".to_string()
204 /// ));
205 /// }
206 ///
207 /// // ... rest of implementation
208 /// Ok(self)
209 /// }
210 /// }
211 /// ```
212 #[error("Invalid argument {0}: {0}")]
213 InvalidArgument(String),
214}
215
216// ============================================================================
217// Error Conversion Implementations
218// ============================================================================
219
220/// Automatic conversion from `sqlx::Error` to `Error::DatabaseError`.
221///
222/// This is provided automatically by the `#[from]` attribute on the
223/// `DatabaseError` variant. It enables using the `?` operator to propagate
224/// sqlx errors as Bottle ORM errors.
225///
226/// # Example
227///
228/// ```rust,ignore
229/// async fn example(db: &Database) -> Result<Vec<User>, Error> {
230/// // sqlx::Error is automatically converted to Error via ?
231/// let users = db.model::<User>().scan().await?;
232/// Ok(users)
233/// }
234/// ```
235
236// ============================================================================
237// Helper Functions and Traits
238// ============================================================================
239
240impl Error {
241 /// Creates an `InvalidData` error from a string slice.
242 ///
243 /// This is a convenience method to avoid calling `.to_string()` manually.
244 ///
245 /// # Arguments
246 ///
247 /// * `msg` - The error message
248 ///
249 /// # Example
250 ///
251 /// ```rust,ignore
252 /// fn validate(value: i32) -> Result<(), Error> {
253 /// if value < 0 {
254 /// return Err(Error::invalid_data("Value must be positive"));
255 /// }
256 /// Ok(())
257 /// }
258 /// ```
259 pub fn invalid_data(msg: &str) -> Self {
260 Error::InvalidData(msg.to_string())
261 }
262
263 /// Creates an `InvalidArgument` error from a string slice.
264 ///
265 /// This is a convenience method to avoid calling `.to_string()` manually.
266 ///
267 /// # Arguments
268 ///
269 /// * `msg` - The error message
270 ///
271 /// # Example
272 ///
273 /// ```rust,ignore
274 /// fn set_limit(limit: isize) -> Result<(), Error> {
275 /// if limit < 0 {
276 /// return Err(Error::invalid_argument("Limit cannot be negative"));
277 /// }
278 /// Ok(())
279 /// }
280 /// ```
281 pub fn invalid_argument(msg: &str) -> Self {
282 Error::InvalidArgument(msg.to_string())
283 }
284
285 /// Creates a `Conversion` error from a string slice.
286 ///
287 /// This is a convenience method to avoid calling `.to_string()` manually.
288 ///
289 /// # Arguments
290 ///
291 /// * `msg` - The error message
292 ///
293 /// # Example
294 ///
295 /// ```rust,ignore
296 /// fn parse_value(value: &str) -> Result<i32, Error> {
297 /// value.parse::<i32>()
298 /// .map_err(|_| Error::conversion("Invalid integer format"))
299 /// }
300 /// ```
301 pub fn conversion(msg: &str) -> Self {
302 Error::Conversion(msg.to_string())
303 }
304}