Skip to main content

oxigdal_postgis/
error.rs

1//! Error types for OxiGDAL PostGIS operations
2//!
3//! This module provides comprehensive error types for PostgreSQL/PostGIS integration.
4//! All error types implement [`std::error::Error`] via [`thiserror`].
5
6use thiserror::Error;
7
8/// The main result type for PostGIS operations
9pub type Result<T> = std::result::Result<T, PostGisError>;
10
11/// The main error type for PostGIS operations
12#[derive(Debug, Error)]
13pub enum PostGisError {
14    /// Connection error occurred
15    #[error("Connection error: {0}")]
16    Connection(#[from] ConnectionError),
17
18    /// Query execution error
19    #[error("Query error: {0}")]
20    Query(#[from] QueryError),
21
22    /// Type conversion error
23    #[error("Conversion error: {0}")]
24    Conversion(#[from] ConversionError),
25
26    /// Transaction error
27    #[error("Transaction error: {0}")]
28    Transaction(#[from] TransactionError),
29
30    /// WKB encoding/decoding error
31    #[error("WKB error: {0}")]
32    Wkb(#[from] WkbError),
33
34    /// SQL generation error
35    #[error("SQL error: {0}")]
36    Sql(#[from] SqlError),
37
38    /// OxiGDAL core error
39    #[error("Core error: {0}")]
40    Core(#[from] oxigdal_core::error::OxiGdalError),
41
42    /// Invalid parameter
43    #[error("Invalid parameter '{parameter}': {message}")]
44    InvalidParameter {
45        /// The parameter name
46        parameter: &'static str,
47        /// Error message
48        message: String,
49    },
50
51    /// Operation not supported
52    #[error("Not supported: {operation}")]
53    NotSupported {
54        /// The unsupported operation
55        operation: String,
56    },
57}
58
59/// Connection-related errors
60#[derive(Debug, Error)]
61pub enum ConnectionError {
62    /// Failed to establish connection
63    #[error("Failed to connect to database: {message}")]
64    ConnectionFailed {
65        /// Error message
66        message: String,
67    },
68
69    /// Invalid connection string
70    #[error("Invalid connection string: {message}")]
71    InvalidConnectionString {
72        /// Error message
73        message: String,
74    },
75
76    /// Connection pool error
77    #[error("Connection pool error: {message}")]
78    PoolError {
79        /// Error message
80        message: String,
81    },
82
83    /// Connection timeout
84    #[error("Connection timeout after {seconds} seconds")]
85    Timeout {
86        /// Timeout duration in seconds
87        seconds: u64,
88    },
89
90    /// SSL/TLS error
91    #[error("SSL/TLS error: {message}")]
92    Ssl {
93        /// Error message
94        message: String,
95    },
96
97    /// Authentication failed
98    #[error("Authentication failed: {message}")]
99    AuthenticationFailed {
100        /// Error message
101        message: String,
102    },
103
104    /// Database not found
105    #[error("Database not found: {database}")]
106    DatabaseNotFound {
107        /// Database name
108        database: String,
109    },
110
111    /// PostGIS extension not installed
112    #[error("PostGIS extension not installed or enabled")]
113    PostGisNotInstalled,
114}
115
116/// Query execution errors
117#[derive(Debug, Error)]
118pub enum QueryError {
119    /// Query execution failed
120    #[error("Query execution failed: {message}")]
121    ExecutionFailed {
122        /// Error message
123        message: String,
124    },
125
126    /// Syntax error in SQL query
127    #[error("SQL syntax error: {message}")]
128    SyntaxError {
129        /// Error message
130        message: String,
131    },
132
133    /// Table not found
134    #[error("Table not found: {table}")]
135    TableNotFound {
136        /// Table name
137        table: String,
138    },
139
140    /// Column not found
141    #[error("Column not found: {column} in table {table}")]
142    ColumnNotFound {
143        /// Column name
144        column: String,
145        /// Table name
146        table: String,
147    },
148
149    /// No rows returned
150    #[error("No rows returned for query")]
151    NoRows,
152
153    /// Too many rows returned
154    #[error("Expected {expected} rows, got {actual}")]
155    TooManyRows {
156        /// Expected number of rows
157        expected: usize,
158        /// Actual number of rows
159        actual: usize,
160    },
161
162    /// Invalid spatial reference system
163    #[error("Invalid SRID: {srid}")]
164    InvalidSrid {
165        /// SRID value
166        srid: i32,
167    },
168
169    /// Spatial index not found
170    #[error("Spatial index not found for table: {table}")]
171    SpatialIndexNotFound {
172        /// Table name
173        table: String,
174    },
175
176    /// Query timeout
177    #[error("Query timeout after {seconds} seconds")]
178    Timeout {
179        /// Timeout duration in seconds
180        seconds: u64,
181    },
182}
183
184/// Type conversion errors
185#[derive(Debug, Error)]
186pub enum ConversionError {
187    /// Failed to convert PostgreSQL type to OxiGDAL type
188    #[error("Failed to convert from PostgreSQL type '{pg_type}' to OxiGDAL type: {message}")]
189    FromPostgres {
190        /// PostgreSQL type name
191        pg_type: String,
192        /// Error message
193        message: String,
194    },
195
196    /// Failed to convert OxiGDAL type to PostgreSQL type
197    #[error("Failed to convert from OxiGDAL type to PostgreSQL type '{pg_type}': {message}")]
198    ToPostgres {
199        /// PostgreSQL type name
200        pg_type: String,
201        /// Error message
202        message: String,
203    },
204
205    /// Unsupported geometry type
206    #[error("Unsupported geometry type: {geometry_type}")]
207    UnsupportedGeometry {
208        /// Geometry type name
209        geometry_type: String,
210    },
211
212    /// Invalid SRID
213    #[error("Invalid SRID value: {srid}")]
214    InvalidSrid {
215        /// SRID value
216        srid: i32,
217    },
218
219    /// NULL value encountered
220    #[error("Unexpected NULL value in column: {column}")]
221    UnexpectedNull {
222        /// Column name
223        column: String,
224    },
225
226    /// Type mismatch
227    #[error("Type mismatch: expected {expected}, got {actual}")]
228    TypeMismatch {
229        /// Expected type
230        expected: String,
231        /// Actual type
232        actual: String,
233    },
234
235    /// Invalid dimension
236    #[error("Invalid geometry dimension: {dimension}")]
237    InvalidDimension {
238        /// Dimension value
239        dimension: u32,
240    },
241}
242
243/// Transaction-related errors
244#[derive(Debug, Error)]
245pub enum TransactionError {
246    /// Failed to begin transaction
247    #[error("Failed to begin transaction: {message}")]
248    BeginFailed {
249        /// Error message
250        message: String,
251    },
252
253    /// Failed to commit transaction
254    #[error("Failed to commit transaction: {message}")]
255    CommitFailed {
256        /// Error message
257        message: String,
258    },
259
260    /// Failed to rollback transaction
261    #[error("Failed to rollback transaction: {message}")]
262    RollbackFailed {
263        /// Error message
264        message: String,
265    },
266
267    /// Transaction already in progress
268    #[error("Transaction already in progress")]
269    AlreadyInTransaction,
270
271    /// No active transaction
272    #[error("No active transaction")]
273    NoActiveTransaction,
274
275    /// Savepoint error
276    #[error("Savepoint error: {message}")]
277    SavepointError {
278        /// Error message
279        message: String,
280    },
281
282    /// Deadlock detected
283    #[error("Deadlock detected: {message}")]
284    Deadlock {
285        /// Error message
286        message: String,
287    },
288}
289
290/// WKB encoding/decoding errors
291#[derive(Debug, Error)]
292pub enum WkbError {
293    /// Invalid WKB format
294    #[error("Invalid WKB format: {message}")]
295    InvalidFormat {
296        /// Error message
297        message: String,
298    },
299
300    /// Invalid byte order
301    #[error("Invalid byte order marker: {byte}")]
302    InvalidByteOrder {
303        /// Byte order value
304        byte: u8,
305    },
306
307    /// Unsupported geometry type
308    #[error("Unsupported WKB geometry type: {type_code}")]
309    UnsupportedGeometryType {
310        /// WKB type code
311        type_code: u32,
312    },
313
314    /// Invalid coordinates
315    #[error("Invalid coordinates: {message}")]
316    InvalidCoordinates {
317        /// Error message
318        message: String,
319    },
320
321    /// Buffer too short
322    #[error("Buffer too short: expected at least {expected} bytes, got {actual}")]
323    BufferTooShort {
324        /// Expected size
325        expected: usize,
326        /// Actual size
327        actual: usize,
328    },
329
330    /// Invalid ring
331    #[error("Invalid ring: {message}")]
332    InvalidRing {
333        /// Error message
334        message: String,
335    },
336
337    /// Encoding error
338    #[error("Failed to encode geometry to WKB: {message}")]
339    EncodingFailed {
340        /// Error message
341        message: String,
342    },
343
344    /// Decoding error
345    #[error("Failed to decode WKB: {message}")]
346    DecodingFailed {
347        /// Error message
348        message: String,
349    },
350}
351
352/// SQL generation errors
353#[derive(Debug, Error)]
354pub enum SqlError {
355    /// Invalid identifier
356    #[error("Invalid SQL identifier: {identifier}")]
357    InvalidIdentifier {
358        /// The invalid identifier
359        identifier: String,
360    },
361
362    /// SQL injection attempt detected
363    #[error("Potential SQL injection detected in: {input}")]
364    InjectionAttempt {
365        /// The suspicious input
366        input: String,
367    },
368
369    /// Invalid table name
370    #[error("Invalid table name: {table}")]
371    InvalidTableName {
372        /// Table name
373        table: String,
374    },
375
376    /// Invalid column name
377    #[error("Invalid column name: {column}")]
378    InvalidColumnName {
379        /// Column name
380        column: String,
381    },
382
383    /// Invalid spatial function
384    #[error("Invalid spatial function: {function}")]
385    InvalidSpatialFunction {
386        /// Function name
387        function: String,
388    },
389
390    /// Parameter binding error
391    #[error("Parameter binding error: {message}")]
392    ParameterBindingError {
393        /// Error message
394        message: String,
395    },
396}
397
398// Implement conversions from external error types
399
400impl From<tokio_postgres::Error> for PostGisError {
401    fn from(err: tokio_postgres::Error) -> Self {
402        Self::Query(QueryError::ExecutionFailed {
403            message: err.to_string(),
404        })
405    }
406}
407
408impl From<deadpool_postgres::PoolError> for PostGisError {
409    fn from(err: deadpool_postgres::PoolError) -> Self {
410        Self::Connection(ConnectionError::PoolError {
411            message: err.to_string(),
412        })
413    }
414}
415
416impl From<std::io::Error> for PostGisError {
417    fn from(err: std::io::Error) -> Self {
418        Self::Connection(ConnectionError::ConnectionFailed {
419            message: err.to_string(),
420        })
421    }
422}
423
424#[cfg(test)]
425mod tests {
426    use super::*;
427
428    #[test]
429    fn test_error_display() {
430        let err = PostGisError::InvalidParameter {
431            parameter: "table",
432            message: "must not be empty".to_string(),
433        };
434        assert!(err.to_string().contains("table"));
435        assert!(err.to_string().contains("must not be empty"));
436    }
437
438    #[test]
439    fn test_connection_error() {
440        let err = ConnectionError::DatabaseNotFound {
441            database: "test_db".to_string(),
442        };
443        assert!(err.to_string().contains("test_db"));
444    }
445
446    #[test]
447    fn test_query_error() {
448        let err = QueryError::TableNotFound {
449            table: "buildings".to_string(),
450        };
451        assert!(err.to_string().contains("buildings"));
452    }
453
454    #[test]
455    fn test_conversion_error() {
456        let err = ConversionError::UnsupportedGeometry {
457            geometry_type: "Unknown".to_string(),
458        };
459        assert!(err.to_string().contains("Unknown"));
460    }
461
462    #[test]
463    fn test_wkb_error() {
464        let err = WkbError::BufferTooShort {
465            expected: 100,
466            actual: 50,
467        };
468        assert!(err.to_string().contains("100"));
469        assert!(err.to_string().contains("50"));
470    }
471
472    #[test]
473    fn test_error_conversion() {
474        let conn_err = ConnectionError::ConnectionFailed {
475            message: "test".to_string(),
476        };
477        let postgis_err: PostGisError = conn_err.into();
478        assert!(matches!(
479            postgis_err,
480            PostGisError::Connection(ConnectionError::ConnectionFailed { .. })
481        ));
482    }
483}