use thiserror::Error;
pub type Result<T> = std::result::Result<T, PostGisError>;
#[derive(Debug, Error)]
pub enum PostGisError {
#[error("Connection error: {0}")]
Connection(#[from] ConnectionError),
#[error("Query error: {0}")]
Query(#[from] QueryError),
#[error("Conversion error: {0}")]
Conversion(#[from] ConversionError),
#[error("Transaction error: {0}")]
Transaction(#[from] TransactionError),
#[error("WKB error: {0}")]
Wkb(#[from] WkbError),
#[error("SQL error: {0}")]
Sql(#[from] SqlError),
#[error("Core error: {0}")]
Core(#[from] oxigdal_core::error::OxiGdalError),
#[error("Invalid parameter '{parameter}': {message}")]
InvalidParameter {
parameter: &'static str,
message: String,
},
#[error("Not supported: {operation}")]
NotSupported {
operation: String,
},
}
#[derive(Debug, Error)]
pub enum ConnectionError {
#[error("Failed to connect to database: {message}")]
ConnectionFailed {
message: String,
},
#[error("Invalid connection string: {message}")]
InvalidConnectionString {
message: String,
},
#[error("Connection pool error: {message}")]
PoolError {
message: String,
},
#[error("Connection timeout after {seconds} seconds")]
Timeout {
seconds: u64,
},
#[error("SSL/TLS error: {message}")]
Ssl {
message: String,
},
#[error("Authentication failed: {message}")]
AuthenticationFailed {
message: String,
},
#[error("Database not found: {database}")]
DatabaseNotFound {
database: String,
},
#[error("PostGIS extension not installed or enabled")]
PostGisNotInstalled,
}
#[derive(Debug, Error)]
pub enum QueryError {
#[error("Query execution failed: {message}")]
ExecutionFailed {
message: String,
},
#[error("SQL syntax error: {message}")]
SyntaxError {
message: String,
},
#[error("Table not found: {table}")]
TableNotFound {
table: String,
},
#[error("Column not found: {column} in table {table}")]
ColumnNotFound {
column: String,
table: String,
},
#[error("No rows returned for query")]
NoRows,
#[error("Expected {expected} rows, got {actual}")]
TooManyRows {
expected: usize,
actual: usize,
},
#[error("Invalid SRID: {srid}")]
InvalidSrid {
srid: i32,
},
#[error("Spatial index not found for table: {table}")]
SpatialIndexNotFound {
table: String,
},
#[error("Query timeout after {seconds} seconds")]
Timeout {
seconds: u64,
},
}
#[derive(Debug, Error)]
pub enum ConversionError {
#[error("Failed to convert from PostgreSQL type '{pg_type}' to OxiGDAL type: {message}")]
FromPostgres {
pg_type: String,
message: String,
},
#[error("Failed to convert from OxiGDAL type to PostgreSQL type '{pg_type}': {message}")]
ToPostgres {
pg_type: String,
message: String,
},
#[error("Unsupported geometry type: {geometry_type}")]
UnsupportedGeometry {
geometry_type: String,
},
#[error("Invalid SRID value: {srid}")]
InvalidSrid {
srid: i32,
},
#[error("Unexpected NULL value in column: {column}")]
UnexpectedNull {
column: String,
},
#[error("Type mismatch: expected {expected}, got {actual}")]
TypeMismatch {
expected: String,
actual: String,
},
#[error("Invalid geometry dimension: {dimension}")]
InvalidDimension {
dimension: u32,
},
}
#[derive(Debug, Error)]
pub enum TransactionError {
#[error("Failed to begin transaction: {message}")]
BeginFailed {
message: String,
},
#[error("Failed to commit transaction: {message}")]
CommitFailed {
message: String,
},
#[error("Failed to rollback transaction: {message}")]
RollbackFailed {
message: String,
},
#[error("Transaction already in progress")]
AlreadyInTransaction,
#[error("No active transaction")]
NoActiveTransaction,
#[error("Savepoint error: {message}")]
SavepointError {
message: String,
},
#[error("Deadlock detected: {message}")]
Deadlock {
message: String,
},
}
#[derive(Debug, Error)]
pub enum WkbError {
#[error("Invalid WKB format: {message}")]
InvalidFormat {
message: String,
},
#[error("Invalid byte order marker: {byte}")]
InvalidByteOrder {
byte: u8,
},
#[error("Unsupported WKB geometry type: {type_code}")]
UnsupportedGeometryType {
type_code: u32,
},
#[error("Invalid coordinates: {message}")]
InvalidCoordinates {
message: String,
},
#[error("Buffer too short: expected at least {expected} bytes, got {actual}")]
BufferTooShort {
expected: usize,
actual: usize,
},
#[error("Invalid ring: {message}")]
InvalidRing {
message: String,
},
#[error("Failed to encode geometry to WKB: {message}")]
EncodingFailed {
message: String,
},
#[error("Failed to decode WKB: {message}")]
DecodingFailed {
message: String,
},
}
#[derive(Debug, Error)]
pub enum SqlError {
#[error("Invalid SQL identifier: {identifier}")]
InvalidIdentifier {
identifier: String,
},
#[error("Potential SQL injection detected in: {input}")]
InjectionAttempt {
input: String,
},
#[error("Invalid table name: {table}")]
InvalidTableName {
table: String,
},
#[error("Invalid column name: {column}")]
InvalidColumnName {
column: String,
},
#[error("Invalid spatial function: {function}")]
InvalidSpatialFunction {
function: String,
},
#[error("Parameter binding error: {message}")]
ParameterBindingError {
message: String,
},
}
impl From<tokio_postgres::Error> for PostGisError {
fn from(err: tokio_postgres::Error) -> Self {
Self::Query(QueryError::ExecutionFailed {
message: err.to_string(),
})
}
}
impl From<deadpool_postgres::PoolError> for PostGisError {
fn from(err: deadpool_postgres::PoolError) -> Self {
Self::Connection(ConnectionError::PoolError {
message: err.to_string(),
})
}
}
impl From<std::io::Error> for PostGisError {
fn from(err: std::io::Error) -> Self {
Self::Connection(ConnectionError::ConnectionFailed {
message: err.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = PostGisError::InvalidParameter {
parameter: "table",
message: "must not be empty".to_string(),
};
assert!(err.to_string().contains("table"));
assert!(err.to_string().contains("must not be empty"));
}
#[test]
fn test_connection_error() {
let err = ConnectionError::DatabaseNotFound {
database: "test_db".to_string(),
};
assert!(err.to_string().contains("test_db"));
}
#[test]
fn test_query_error() {
let err = QueryError::TableNotFound {
table: "buildings".to_string(),
};
assert!(err.to_string().contains("buildings"));
}
#[test]
fn test_conversion_error() {
let err = ConversionError::UnsupportedGeometry {
geometry_type: "Unknown".to_string(),
};
assert!(err.to_string().contains("Unknown"));
}
#[test]
fn test_wkb_error() {
let err = WkbError::BufferTooShort {
expected: 100,
actual: 50,
};
assert!(err.to_string().contains("100"));
assert!(err.to_string().contains("50"));
}
#[test]
fn test_error_conversion() {
let conn_err = ConnectionError::ConnectionFailed {
message: "test".to_string(),
};
let postgis_err: PostGisError = conn_err.into();
assert!(matches!(
postgis_err,
PostGisError::Connection(ConnectionError::ConnectionFailed { .. })
));
}
}