use std::ffi::{CString, c_char};
use arrow_schema::ArrowError;
use geopackage::{
Error,
core::{Error as CoreError, GeometryError, GpbError, TileError},
};
#[repr(i32)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
Ok = 0,
Io = 1,
NotAGeoPackage = 2,
NotFound = 3,
AlreadyExists = 4,
InvalidArgument = 5,
Unsupported = 6,
Constraint = 7,
HandleInUse = 8,
BadArgument = 9,
Other = 99,
}
impl From<&Error> for Status {
fn from(error: &Error) -> Self {
match error {
Error::Sqlite(sqlite)
if sqlite.sqlite_error_code() == Some(rusqlite::ErrorCode::ConstraintViolation) =>
{
Self::Constraint
}
Error::Sqlite(sqlite)
if sqlite.sqlite_error_code() == Some(rusqlite::ErrorCode::NotADatabase) =>
{
Self::NotAGeoPackage
}
Error::Sqlite(_) | Error::ExtentPersist { .. } => Self::Io,
Error::NotAGeoPackage { .. } => Self::NotAGeoPackage,
Error::NoSuchTable { .. }
| Error::NoSuchColumn { .. }
| Error::NoSuchRow { .. }
| Error::NoSuchLayer { .. }
| Error::UnknownSrs { .. }
| Error::NoSuchMetadata { .. }
| Error::UnknownEpsgCode { .. }
| Error::UnknownZoomLevel { .. }
| Error::NoSpatialIndex { .. }
| Error::NoTileMatrixSet { .. }
| Error::NoGeometryColumn { .. }
| Error::NoPrimaryKey { .. }
| Error::NoCoverageAncillary { .. } => Self::NotFound,
Error::AlreadyExists(_)
| Error::TableAlreadyExists { .. }
| Error::SpatialIndexExists { .. } => Self::AlreadyExists,
Error::UnsupportedExtension { .. }
| Error::UnsupportedArrowType { .. }
| Error::GeometryValueUnsupported { .. }
| Error::ZoomOtherNotEnabled { .. }
| Error::TileFormatNotAllowed { .. }
| Error::UnwritableCoveragePayload { .. }
| Error::RtreeUnavailable => Self::Unsupported,
Error::ColumnConstraintViolation { .. }
| Error::ZmViolation { .. }
| Error::GeometryTypeMismatch { .. }
| Error::NonConformantRelationName { .. }
| Error::ReservedTablePrefix { .. }
| Error::MetadataCycle { .. }
| Error::SelfParentedMetadata { .. }
| Error::FloatCoverageScaled { .. } => Self::Constraint,
Error::ValueTypeMismatch { .. }
| Error::ArrowValueMismatch { .. }
| Error::NonBooleanInteger { .. }
| Error::InvalidDateTimeValue { .. }
| Error::InvalidZmFlag { .. }
| Error::InvalidColumnConstraint { .. }
| Error::UnknownGeometryType { .. }
| Error::UnknownReferenceScope { .. }
| Error::ValueCountMismatch { .. }
| Error::DuplicateUpdateColumn { .. }
| Error::MissingGeometrySpec { .. }
| Error::UnexpectedGeometrySpec { .. }
| Error::NanEpoch { .. }
| Error::NanAncillaryValue { .. }
| Error::WrongDataType { .. }
| Error::GeometryNotProjected => Self::InvalidArgument,
Error::Tile(tile) => tile_status(tile),
Error::Core(core) => core_status(core),
Error::Arrow(arrow) => arrow_status(arrow),
_ => Self::Other,
}
}
}
fn arrow_status(error: &ArrowError) -> Status {
match error {
ArrowError::ExternalError(inner) => inner
.downcast_ref::<Error>()
.map_or(Status::Other, Status::from),
ArrowError::InvalidArgumentError(_)
| ArrowError::SchemaError(_)
| ArrowError::CastError(_)
| ArrowError::ParseError(_)
| ArrowError::CDataInterface(_) => Status::InvalidArgument,
ArrowError::NotYetImplemented(_) => Status::Unsupported,
ArrowError::IoError(..) => Status::Io,
_ => Status::Other,
}
}
fn core_status(error: &CoreError) -> Status {
match error {
CoreError::Gpb(gpb) => gpb_status(gpb),
CoreError::Geometry(geometry) => geometry_status(geometry),
CoreError::Tile(tile) => tile_status(tile),
CoreError::InvalidIdentifier(_) => Status::InvalidArgument,
_ => Status::Other,
}
}
fn gpb_status(error: &GpbError) -> Status {
match error {
GpbError::Truncated { .. }
| GpbError::BadMagic(..)
| GpbError::InvalidEnvelopeIndicator(_) => Status::InvalidArgument,
GpbError::UnsupportedVersion(_) => Status::Unsupported,
_ => Status::Other,
}
}
fn geometry_status(error: &GeometryError) -> Status {
match error {
GeometryError::Header(gpb) => gpb_status(gpb),
GeometryError::Body(_)
| GeometryError::TruncatedWkb
| GeometryError::UnknownWkbType(_)
| GeometryError::AbstractWkbType(_)
| GeometryError::TruncatedAt { .. }
| GeometryError::InvalidByteOrder { .. }
| GeometryError::NestingTooDeep
| GeometryError::InteriorRingsWithoutExterior => Status::InvalidArgument,
GeometryError::NonLinearMember { .. } => Status::Unsupported,
GeometryError::EncodeWkb(_) => Status::Other,
_ => Status::Other,
}
}
fn tile_status(error: &TileError) -> Status {
match error {
TileError::InvalidExtent { .. }
| TileError::NegativeZoomLevel { .. }
| TileError::NonPositiveDimension { .. }
| TileError::NonPositivePixelSize { .. }
| TileError::PixelSizeNotDescending { .. }
| TileError::ExtentMismatch { .. }
| TileError::DuplicateZoomLevel { .. }
| TileError::PayloadSizeMismatch { .. }
| TileError::CoverageProfileViolation { .. } => Status::Constraint,
TileError::CoordOutsideMatrix { .. }
| TileError::UnreadablePayload { .. }
| TileError::InvalidZoomRange { .. } => Status::InvalidArgument,
TileError::NotAnXyzGrid { .. } => Status::Unsupported,
_ => Status::Other,
}
}
#[repr(C)]
pub struct gpkg_error_t {
pub code: Status,
pub message: *mut c_char,
}
pub(crate) unsafe fn set_error(slot: *mut gpkg_error_t, code: Status, message: &str) {
if slot.is_null() {
return;
}
let owned = CString::new(message).unwrap_or_else(|_| {
CString::new("error message contained an interior NUL").unwrap_or_default()
});
let slot = unsafe { &mut *slot };
slot.code = code;
slot.message = owned.into_raw();
}
pub(crate) unsafe fn set_library_error(slot: *mut gpkg_error_t, error: &Error) {
unsafe { set_error(slot, Status::from(error), &error.to_string()) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn gpkg_error_clear(error: *mut gpkg_error_t) {
if error.is_null() {
return;
}
let error = unsafe { &mut *error };
if !error.message.is_null() {
drop(unsafe { CString::from_raw(error.message) });
error.message = std::ptr::null_mut();
}
error.code = Status::Ok;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_arrow_failure_classifies_by_variant() {
assert_eq!(
arrow_status(&ArrowError::SchemaError("mismatch".to_owned())),
Status::InvalidArgument
);
assert_eq!(
arrow_status(&ArrowError::NotYetImplemented("later".to_owned())),
Status::Unsupported
);
assert_eq!(
arrow_status(&ArrowError::ParquetError("elsewhere".to_owned())),
Status::Other
);
}
#[test]
fn an_arrow_failure_wrapping_one_of_ours_keeps_its_category() {
let inner = Error::NoSuchLayer {
table_name: "places".to_owned(),
};
let wrapped = ArrowError::ExternalError(Box::new(inner));
assert_eq!(arrow_status(&wrapped), Status::NotFound);
let foreign = ArrowError::ExternalError(Box::new(std::fmt::Error));
assert_eq!(arrow_status(&foreign), Status::Other);
}
#[test]
fn a_coverage_profile_violation_is_a_constraint() {
let error = TileError::CoverageProfileViolation {
requirement: 5,
detail: "PNG coverage tile is 8-bit".to_owned(),
};
assert_eq!(tile_status(&error), Status::Constraint);
}
#[test]
fn errors_no_c_call_reaches_yet_have_a_category() {
let table_name = "dem".to_owned();
let cases = [
(
Error::FloatCoverageScaled {
table_name: table_name.clone(),
scale: 2.0,
offset: 0.0,
},
Status::Constraint,
),
(
Error::NanAncillaryValue {
table_name: table_name.clone(),
column: "gpkg_2d_gridded_tile_ancillary.scale",
},
Status::InvalidArgument,
),
(
Error::UnwritableCoveragePayload {
table_name: table_name.clone(),
reason: "Deflate compression".to_owned(),
},
Status::Unsupported,
),
(
Error::NoCoverageAncillary {
table_name: table_name.clone(),
},
Status::NotFound,
),
(
Error::Core(CoreError::Geometry(
GeometryError::InteriorRingsWithoutExterior,
)),
Status::InvalidArgument,
),
];
for (error, status) in cases {
assert_eq!(Status::from(&error), status, "{error}");
}
}
}