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}