use oxigdal_core::error::OxiGdalError;
use thiserror::Error;
pub type Result<T> = core::result::Result<T, ShapefileError>;
#[derive(Debug, Error)]
pub enum ShapefileError {
#[error("Invalid Shapefile header: {message}")]
InvalidHeader {
message: String,
},
#[error("Invalid file code: expected 9994, got {actual}")]
InvalidFileCode {
actual: i32,
},
#[error("Invalid version: {version}")]
InvalidVersion {
version: i32,
},
#[error("Unsupported shape type: {shape_type}")]
UnsupportedShapeType {
shape_type: i32,
},
#[error("Invalid shape type: {shape_type}")]
InvalidShapeType {
shape_type: i32,
},
#[error("Invalid geometry: {message}")]
InvalidGeometry {
message: String,
record: Option<usize>,
},
#[error("Invalid coordinates: {message}")]
InvalidCoordinates {
message: String,
position: Option<usize>,
},
#[error("Invalid bounding box: {message}")]
InvalidBbox {
message: String,
},
#[error("DBF error: {message}")]
DbfError {
message: String,
field: Option<String>,
record: Option<usize>,
},
#[error("Invalid DBF header: {message}")]
InvalidDbfHeader {
message: String,
},
#[error("Invalid field descriptor: {message}")]
InvalidFieldDescriptor {
message: String,
field: Option<String>,
},
#[error("Invalid field value: {message}")]
InvalidFieldValue {
message: String,
field: String,
record: usize,
},
#[error("Encoding error: {message}")]
EncodingError {
message: String,
code_page: Option<u8>,
},
#[error("SHX index error: {message}")]
ShxError {
message: String,
record: Option<usize>,
},
#[error("Record count mismatch: .shp has {shp_count} records, .dbf has {dbf_count} records")]
RecordMismatch {
shp_count: usize,
dbf_count: usize,
},
#[error("Missing required file: {file_type}")]
MissingFile {
file_type: String,
},
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Unexpected end of file: {message}")]
UnexpectedEof {
message: String,
},
#[error("Validation error: {message}")]
Validation {
message: String,
path: Option<String>,
},
#[error("Topology error: {message}")]
Topology {
message: String,
},
#[error("Out of memory: {message}")]
OutOfMemory {
message: String,
},
#[error("Limit exceeded: {message}")]
LimitExceeded {
message: String,
limit: usize,
actual: usize,
},
#[error("OxiGDAL error: {0}")]
OxiGdal(#[from] OxiGdalError),
}
impl ShapefileError {
pub fn invalid_header<S: Into<String>>(message: S) -> Self {
Self::InvalidHeader {
message: message.into(),
}
}
pub fn invalid_geometry<S: Into<String>>(message: S) -> Self {
Self::InvalidGeometry {
message: message.into(),
record: None,
}
}
pub fn invalid_geometry_at<S: Into<String>>(message: S, record: usize) -> Self {
Self::InvalidGeometry {
message: message.into(),
record: Some(record),
}
}
pub fn invalid_coordinates<S: Into<String>>(message: S) -> Self {
Self::InvalidCoordinates {
message: message.into(),
position: None,
}
}
pub fn invalid_coordinates_at<S: Into<String>>(message: S, position: usize) -> Self {
Self::InvalidCoordinates {
message: message.into(),
position: Some(position),
}
}
pub fn dbf_error<S: Into<String>>(message: S) -> Self {
Self::DbfError {
message: message.into(),
field: None,
record: None,
}
}
pub fn dbf_error_at<S: Into<String>, F: Into<String>>(
message: S,
field: F,
record: usize,
) -> Self {
Self::DbfError {
message: message.into(),
field: Some(field.into()),
record: Some(record),
}
}
pub fn encoding_error<S: Into<String>>(message: S) -> Self {
Self::EncodingError {
message: message.into(),
code_page: None,
}
}
pub fn validation<S: Into<String>>(message: S) -> Self {
Self::Validation {
message: message.into(),
path: None,
}
}
pub fn validation_at<S: Into<String>, P: Into<String>>(message: S, path: P) -> Self {
Self::Validation {
message: message.into(),
path: Some(path.into()),
}
}
pub fn topology<S: Into<String>>(message: S) -> Self {
Self::Topology {
message: message.into(),
}
}
pub fn limit_exceeded<S: Into<String>>(message: S, limit: usize, actual: usize) -> Self {
Self::LimitExceeded {
message: message.into(),
limit,
actual,
}
}
pub fn unexpected_eof<S: Into<String>>(message: S) -> Self {
Self::UnexpectedEof {
message: message.into(),
}
}
}
#[cfg(test)]
#[allow(clippy::panic)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = ShapefileError::invalid_header("missing file code");
assert!(err.to_string().contains("missing file code"));
let err = ShapefileError::invalid_geometry_at("invalid polygon", 5);
assert!(err.to_string().contains("invalid polygon"));
let err = ShapefileError::InvalidFileCode { actual: 1234 };
assert!(err.to_string().contains("9994"));
assert!(err.to_string().contains("1234"));
}
#[test]
fn test_record_mismatch() {
let err = ShapefileError::RecordMismatch {
shp_count: 100,
dbf_count: 95,
};
assert!(err.to_string().contains("100"));
assert!(err.to_string().contains("95"));
}
#[test]
fn test_dbf_error_construction() {
let err = ShapefileError::dbf_error_at("invalid value", "name", 42);
if let ShapefileError::DbfError {
field,
record,
message,
} = err
{
assert_eq!(field, Some("name".to_string()));
assert_eq!(record, Some(42));
assert_eq!(message, "invalid value");
} else {
panic!("Expected DbfError");
}
}
}