prax_postgres/
error.rs

1//! Error types for PostgreSQL operations.
2
3use prax_query::QueryError;
4use thiserror::Error;
5
6/// Result type for PostgreSQL operations.
7pub type PgResult<T> = Result<T, PgError>;
8
9/// Errors that can occur during PostgreSQL operations.
10#[derive(Error, Debug)]
11pub enum PgError {
12    /// Connection pool error.
13    #[error("pool error: {0}")]
14    Pool(#[from] deadpool_postgres::PoolError),
15
16    /// PostgreSQL error.
17    #[error("postgres error: {0}")]
18    Postgres(#[from] tokio_postgres::Error),
19
20    /// Configuration error.
21    #[error("configuration error: {0}")]
22    Config(String),
23
24    /// Connection error.
25    #[error("connection error: {0}")]
26    Connection(String),
27
28    /// Query execution error.
29    #[error("query error: {0}")]
30    Query(String),
31
32    /// Row deserialization error.
33    #[error("deserialization error: {0}")]
34    Deserialization(String),
35
36    /// Type conversion error.
37    #[error("type conversion error: {0}")]
38    TypeConversion(String),
39
40    /// Timeout error.
41    #[error("operation timed out after {0}ms")]
42    Timeout(u64),
43
44    /// Internal error.
45    #[error("internal error: {0}")]
46    Internal(String),
47}
48
49impl PgError {
50    /// Create a configuration error.
51    pub fn config(message: impl Into<String>) -> Self {
52        Self::Config(message.into())
53    }
54
55    /// Create a connection error.
56    pub fn connection(message: impl Into<String>) -> Self {
57        Self::Connection(message.into())
58    }
59
60    /// Create a query error.
61    pub fn query(message: impl Into<String>) -> Self {
62        Self::Query(message.into())
63    }
64
65    /// Create a deserialization error.
66    pub fn deserialization(message: impl Into<String>) -> Self {
67        Self::Deserialization(message.into())
68    }
69
70    /// Create a type conversion error.
71    pub fn type_conversion(message: impl Into<String>) -> Self {
72        Self::TypeConversion(message.into())
73    }
74
75    /// Check if this is a connection error.
76    pub fn is_connection_error(&self) -> bool {
77        matches!(self, Self::Pool(_) | Self::Connection(_))
78    }
79
80    /// Check if this is a timeout error.
81    pub fn is_timeout(&self) -> bool {
82        matches!(self, Self::Timeout(_))
83    }
84}
85
86impl From<PgError> for QueryError {
87    fn from(err: PgError) -> Self {
88        match err {
89            PgError::Pool(e) => QueryError::connection(e.to_string()),
90            PgError::Postgres(e) => {
91                // Try to categorize PostgreSQL errors
92                let code = e.code();
93                if let Some(code) = code {
94                    let code_str = code.code();
95                    // Unique violation
96                    if code_str == "23505" {
97                        return QueryError::constraint_violation("", e.to_string());
98                    }
99                    // Foreign key violation
100                    if code_str == "23503" {
101                        return QueryError::constraint_violation("", e.to_string());
102                    }
103                    // Not null violation
104                    if code_str == "23502" {
105                        return QueryError::invalid_input("", e.to_string());
106                    }
107                }
108                QueryError::database(e.to_string())
109            }
110            PgError::Config(msg) => QueryError::connection(msg),
111            PgError::Connection(msg) => QueryError::connection(msg),
112            PgError::Query(msg) => QueryError::database(msg),
113            PgError::Deserialization(msg) => QueryError::serialization(msg),
114            PgError::TypeConversion(msg) => QueryError::serialization(msg),
115            PgError::Timeout(ms) => QueryError::timeout(ms),
116            PgError::Internal(msg) => QueryError::internal(msg),
117        }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_error_creation() {
127        let err = PgError::config("invalid URL");
128        assert!(matches!(err, PgError::Config(_)));
129
130        let err = PgError::connection("connection refused");
131        assert!(err.is_connection_error());
132
133        let err = PgError::Timeout(5000);
134        assert!(err.is_timeout());
135    }
136
137    #[test]
138    fn test_into_query_error() {
139        let pg_err = PgError::Timeout(1000);
140        let query_err: QueryError = pg_err.into();
141        assert!(query_err.is_timeout());
142    }
143}