1use thiserror::Error;
7
8pub type Result<T> = std::result::Result<T, PostGisError>;
10
11#[derive(Debug, Error)]
13pub enum PostGisError {
14 #[error("Connection error: {0}")]
16 Connection(#[from] ConnectionError),
17
18 #[error("Query error: {0}")]
20 Query(#[from] QueryError),
21
22 #[error("Conversion error: {0}")]
24 Conversion(#[from] ConversionError),
25
26 #[error("Transaction error: {0}")]
28 Transaction(#[from] TransactionError),
29
30 #[error("WKB error: {0}")]
32 Wkb(#[from] WkbError),
33
34 #[error("SQL error: {0}")]
36 Sql(#[from] SqlError),
37
38 #[error("Core error: {0}")]
40 Core(#[from] oxigdal_core::error::OxiGdalError),
41
42 #[error("Invalid parameter '{parameter}': {message}")]
44 InvalidParameter {
45 parameter: &'static str,
47 message: String,
49 },
50
51 #[error("Not supported: {operation}")]
53 NotSupported {
54 operation: String,
56 },
57}
58
59#[derive(Debug, Error)]
61pub enum ConnectionError {
62 #[error("Failed to connect to database: {message}")]
64 ConnectionFailed {
65 message: String,
67 },
68
69 #[error("Invalid connection string: {message}")]
71 InvalidConnectionString {
72 message: String,
74 },
75
76 #[error("Connection pool error: {message}")]
78 PoolError {
79 message: String,
81 },
82
83 #[error("Connection timeout after {seconds} seconds")]
85 Timeout {
86 seconds: u64,
88 },
89
90 #[error("SSL/TLS error: {message}")]
92 Ssl {
93 message: String,
95 },
96
97 #[error("Authentication failed: {message}")]
99 AuthenticationFailed {
100 message: String,
102 },
103
104 #[error("Database not found: {database}")]
106 DatabaseNotFound {
107 database: String,
109 },
110
111 #[error("PostGIS extension not installed or enabled")]
113 PostGisNotInstalled,
114}
115
116#[derive(Debug, Error)]
118pub enum QueryError {
119 #[error("Query execution failed: {message}")]
121 ExecutionFailed {
122 message: String,
124 },
125
126 #[error("SQL syntax error: {message}")]
128 SyntaxError {
129 message: String,
131 },
132
133 #[error("Table not found: {table}")]
135 TableNotFound {
136 table: String,
138 },
139
140 #[error("Column not found: {column} in table {table}")]
142 ColumnNotFound {
143 column: String,
145 table: String,
147 },
148
149 #[error("No rows returned for query")]
151 NoRows,
152
153 #[error("Expected {expected} rows, got {actual}")]
155 TooManyRows {
156 expected: usize,
158 actual: usize,
160 },
161
162 #[error("Invalid SRID: {srid}")]
164 InvalidSrid {
165 srid: i32,
167 },
168
169 #[error("Spatial index not found for table: {table}")]
171 SpatialIndexNotFound {
172 table: String,
174 },
175
176 #[error("Query timeout after {seconds} seconds")]
178 Timeout {
179 seconds: u64,
181 },
182}
183
184#[derive(Debug, Error)]
186pub enum ConversionError {
187 #[error("Failed to convert from PostgreSQL type '{pg_type}' to OxiGDAL type: {message}")]
189 FromPostgres {
190 pg_type: String,
192 message: String,
194 },
195
196 #[error("Failed to convert from OxiGDAL type to PostgreSQL type '{pg_type}': {message}")]
198 ToPostgres {
199 pg_type: String,
201 message: String,
203 },
204
205 #[error("Unsupported geometry type: {geometry_type}")]
207 UnsupportedGeometry {
208 geometry_type: String,
210 },
211
212 #[error("Invalid SRID value: {srid}")]
214 InvalidSrid {
215 srid: i32,
217 },
218
219 #[error("Unexpected NULL value in column: {column}")]
221 UnexpectedNull {
222 column: String,
224 },
225
226 #[error("Type mismatch: expected {expected}, got {actual}")]
228 TypeMismatch {
229 expected: String,
231 actual: String,
233 },
234
235 #[error("Invalid geometry dimension: {dimension}")]
237 InvalidDimension {
238 dimension: u32,
240 },
241}
242
243#[derive(Debug, Error)]
245pub enum TransactionError {
246 #[error("Failed to begin transaction: {message}")]
248 BeginFailed {
249 message: String,
251 },
252
253 #[error("Failed to commit transaction: {message}")]
255 CommitFailed {
256 message: String,
258 },
259
260 #[error("Failed to rollback transaction: {message}")]
262 RollbackFailed {
263 message: String,
265 },
266
267 #[error("Transaction already in progress")]
269 AlreadyInTransaction,
270
271 #[error("No active transaction")]
273 NoActiveTransaction,
274
275 #[error("Savepoint error: {message}")]
277 SavepointError {
278 message: String,
280 },
281
282 #[error("Deadlock detected: {message}")]
284 Deadlock {
285 message: String,
287 },
288}
289
290#[derive(Debug, Error)]
292pub enum WkbError {
293 #[error("Invalid WKB format: {message}")]
295 InvalidFormat {
296 message: String,
298 },
299
300 #[error("Invalid byte order marker: {byte}")]
302 InvalidByteOrder {
303 byte: u8,
305 },
306
307 #[error("Unsupported WKB geometry type: {type_code}")]
309 UnsupportedGeometryType {
310 type_code: u32,
312 },
313
314 #[error("Invalid coordinates: {message}")]
316 InvalidCoordinates {
317 message: String,
319 },
320
321 #[error("Buffer too short: expected at least {expected} bytes, got {actual}")]
323 BufferTooShort {
324 expected: usize,
326 actual: usize,
328 },
329
330 #[error("Invalid ring: {message}")]
332 InvalidRing {
333 message: String,
335 },
336
337 #[error("Failed to encode geometry to WKB: {message}")]
339 EncodingFailed {
340 message: String,
342 },
343
344 #[error("Failed to decode WKB: {message}")]
346 DecodingFailed {
347 message: String,
349 },
350}
351
352#[derive(Debug, Error)]
354pub enum SqlError {
355 #[error("Invalid SQL identifier: {identifier}")]
357 InvalidIdentifier {
358 identifier: String,
360 },
361
362 #[error("Potential SQL injection detected in: {input}")]
364 InjectionAttempt {
365 input: String,
367 },
368
369 #[error("Invalid table name: {table}")]
371 InvalidTableName {
372 table: String,
374 },
375
376 #[error("Invalid column name: {column}")]
378 InvalidColumnName {
379 column: String,
381 },
382
383 #[error("Invalid spatial function: {function}")]
385 InvalidSpatialFunction {
386 function: String,
388 },
389
390 #[error("Parameter binding error: {message}")]
392 ParameterBindingError {
393 message: String,
395 },
396}
397
398impl 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}