use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[allow(clippy::enum_variant_names)]
pub enum ErrorKind {
ValueError,
KeyError,
TypeError,
OverflowError,
NotImplementedError,
GEOSException,
ApbaseError,
ValidationError,
NativeExtensionError,
NativeExecutionError,
VariogramError,
CrossVariogramError,
SpatialFilterError,
InterpolationError,
CrossValidationError,
IDWInvalidDataError,
IDWInvalidRadiusError,
IDWInvalidPowerError,
IDWInvalidConfigurationError,
IDWAllocationError,
KrigingInvalidDataError,
KrigingInvalidRadiusError,
KrigingInvalidConfigurationError,
KrigingAllocationError,
KrigingCapacityExceededError,
KrigingInvalidModelError,
CoKrigingInvalidDataError,
CoKrigingInvalidRadiusError,
CoKrigingInvalidConfigurationError,
CoKrigingAllocationError,
CoKrigingCapacityExceededError,
CoKrigingInvalidModelError,
CoKrigingNonPSDCoregionalizationError,
SpatialFilterInvalidDataError,
SpatialFilterInvalidRadiusError,
SpatialFilterInvalidConfigurationError,
SpatialFilterAllocationError,
CrossValidationInvalidDataError,
CrossValidationInvalidRadiusError,
CrossValidationInvalidPowerError,
CrossValidationInvalidConfigurationError,
CrossValidationAllocationError,
CrossValidationInvalidModelError,
CrossValidationInvalidSampleIndexError,
VariogramInvalidModelIdError,
VariogramInvalidModelError,
VariogramInvalidRangeError,
VariogramInvalidThreadCountError,
CrossVariogramInsufficientSampleError,
CrossVariogramInvalidAutoModelError,
CrossVariogramInvalidCrossModelError,
}
impl ErrorKind {
pub fn name(self) -> String {
format!("{self:?}")
}
pub const fn bases(self) -> &'static [ErrorKind] {
use ErrorKind::*;
match self {
ValueError | KeyError | TypeError | OverflowError | NotImplementedError | GEOSException
| ApbaseError => &[],
ValidationError => &[ApbaseError, ValueError],
NativeExtensionError | NativeExecutionError | VariogramError | CrossVariogramError
| SpatialFilterError | InterpolationError | CrossValidationError => &[ApbaseError],
IDWInvalidDataError
| IDWInvalidRadiusError
| IDWInvalidPowerError
| IDWInvalidConfigurationError
| IDWAllocationError
| KrigingInvalidDataError
| KrigingInvalidRadiusError
| KrigingInvalidConfigurationError
| KrigingAllocationError
| KrigingCapacityExceededError
| KrigingInvalidModelError
| CoKrigingInvalidDataError
| CoKrigingInvalidRadiusError
| CoKrigingInvalidConfigurationError
| CoKrigingAllocationError
| CoKrigingCapacityExceededError
| CoKrigingInvalidModelError
| CoKrigingNonPSDCoregionalizationError => &[InterpolationError, NativeExecutionError],
SpatialFilterInvalidDataError
| SpatialFilterInvalidRadiusError
| SpatialFilterInvalidConfigurationError
| SpatialFilterAllocationError => &[SpatialFilterError, NativeExecutionError],
CrossValidationInvalidDataError
| CrossValidationInvalidRadiusError
| CrossValidationInvalidPowerError
| CrossValidationInvalidConfigurationError
| CrossValidationAllocationError
| CrossValidationInvalidModelError
| CrossValidationInvalidSampleIndexError => &[CrossValidationError, NativeExecutionError],
VariogramInvalidModelIdError
| VariogramInvalidModelError
| VariogramInvalidRangeError
| VariogramInvalidThreadCountError => &[VariogramError, NativeExecutionError],
CrossVariogramInsufficientSampleError
| CrossVariogramInvalidAutoModelError
| CrossVariogramInvalidCrossModelError => &[CrossVariogramError, NativeExecutionError],
}
}
pub fn is_subclass_of(self, other: ErrorKind) -> bool {
self == other || self.bases().iter().any(|base| base.is_subclass_of(other))
}
pub const fn fixed_message(self) -> Option<&'static str> {
use ErrorKind::*;
Some(match self {
IDWInvalidDataError => "invalid source or target data",
IDWInvalidRadiusError => "radius must be finite and greater than zero",
IDWInvalidPowerError => "power must be finite and greater than zero",
IDWInvalidConfigurationError => "invalid neighbor, grid, or thread configuration",
IDWAllocationError => "native IDW allocation failed",
KrigingInvalidDataError => "invalid source or target data",
KrigingInvalidRadiusError => "invalid radius",
KrigingInvalidConfigurationError => "invalid neighbor configuration",
KrigingAllocationError => "kriging workspace allocation failed",
KrigingCapacityExceededError => "kriging workspace capacity exceeded",
KrigingInvalidModelError => "invalid variogram model",
CoKrigingInvalidDataError => "invalid source or target data",
CoKrigingInvalidRadiusError => "invalid radius",
CoKrigingInvalidConfigurationError => "invalid neighbor configuration",
CoKrigingAllocationError => "cokriging workspace allocation failed",
CoKrigingCapacityExceededError => "cokriging workspace capacity exceeded",
CoKrigingInvalidModelError => "invalid coregionalization model",
CoKrigingNonPSDCoregionalizationError => "coregionalization matrix is not positive semidefinite",
SpatialFilterInvalidDataError => "invalid source data",
SpatialFilterInvalidRadiusError => "radius must be finite and greater than zero",
SpatialFilterInvalidConfigurationError => {
"invalid neighbor, cell, filter, or thread configuration"
}
SpatialFilterAllocationError => "native spatial filter allocation failed",
CrossValidationInvalidDataError => "invalid source or sample data",
CrossValidationInvalidRadiusError => "radius must be finite and greater than zero",
CrossValidationInvalidPowerError => "power must be finite and greater than zero",
CrossValidationInvalidConfigurationError => "invalid neighbor, grid, or thread configuration",
CrossValidationAllocationError => "native cross-validation allocation failed",
CrossValidationInvalidModelError => "invalid variogram model",
CrossValidationInvalidSampleIndexError => "sample_idx contains an out-of-range index",
VariogramInvalidModelIdError => "model_info returned a non-finite model_id",
VariogramInvalidModelError => "invalid model returned by the native extension",
VariogramInvalidRangeError => "variogram returned an invalid range",
VariogramInvalidThreadCountError => "invalid thread count",
CrossVariogramInsufficientSampleError => "fewer than 4 colocated pairs available",
CrossVariogramInvalidAutoModelError => "invalid primary or candidate auto-variogram model",
CrossVariogramInvalidCrossModelError => "invalid cross-variogram model",
_ => return None,
})
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct ApbaseError {
kind: ErrorKind,
message: String,
}
pub type Result<T, E = ApbaseError> = std::result::Result<T, E>;
impl ApbaseError {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
ApbaseError {
kind,
message: message.into(),
}
}
pub fn from_kind(kind: ErrorKind) -> Self {
ApbaseError::new(kind, kind.fixed_message().unwrap_or_default())
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn message(&self) -> &str {
&self.message
}
pub fn is(&self, kind: ErrorKind) -> bool {
self.kind.is_subclass_of(kind)
}
pub(crate) fn value_error(message: impl Into<String>) -> Self {
ApbaseError::new(ErrorKind::ValueError, message)
}
pub(crate) fn validation_error(message: impl Into<String>) -> Self {
ApbaseError::new(ErrorKind::ValidationError, message)
}
pub(crate) fn native_execution_error(message: impl Into<String>) -> Self {
ApbaseError::new(ErrorKind::NativeExecutionError, message)
}
}
impl fmt::Display for ApbaseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.message)
}
}
impl fmt::Debug for ApbaseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}({:?})", self.kind, self.message)
}
}
impl std::error::Error for ApbaseError {}
impl From<geos::Error> for ApbaseError {
fn from(error: geos::Error) -> Self {
let message = match &error {
geos::Error::GeosError((_, Some(message))) => message.clone(),
other => other.to_string(),
};
ApbaseError::new(ErrorKind::GEOSException, message)
}
}
pub type StatusErrors = &'static [(i32, ErrorKind)];
pub fn raise_status_error(status: i32, status_errors: StatusErrors, fallback_message: &str) -> ApbaseError {
match status_errors.iter().find(|(code, _)| *code == status) {
Some((_, kind)) => ApbaseError::from_kind(*kind),
None => ApbaseError::native_execution_error(format!("{fallback_message} (status {status})")),
}
}
pub const IDW_STATUS_ERRORS: StatusErrors = &[
(1, ErrorKind::IDWInvalidDataError),
(2, ErrorKind::IDWInvalidRadiusError),
(3, ErrorKind::IDWInvalidPowerError),
(4, ErrorKind::IDWInvalidConfigurationError),
(5, ErrorKind::IDWAllocationError),
];
pub const KRIGING_STATUS_ERRORS: StatusErrors = &[
(1, ErrorKind::KrigingInvalidDataError),
(2, ErrorKind::KrigingInvalidRadiusError),
(3, ErrorKind::KrigingInvalidConfigurationError),
(4, ErrorKind::KrigingAllocationError),
(5, ErrorKind::KrigingCapacityExceededError),
(6, ErrorKind::KrigingInvalidModelError),
];
pub const COKRIGING_STATUS_ERRORS: StatusErrors = &[
(1, ErrorKind::CoKrigingInvalidDataError),
(2, ErrorKind::CoKrigingInvalidRadiusError),
(3, ErrorKind::CoKrigingInvalidConfigurationError),
(4, ErrorKind::CoKrigingAllocationError),
(5, ErrorKind::CoKrigingCapacityExceededError),
(6, ErrorKind::CoKrigingInvalidModelError),
(7, ErrorKind::CoKrigingNonPSDCoregionalizationError),
];
pub const FILTER_STATUS_ERRORS: StatusErrors = &[
(1, ErrorKind::SpatialFilterInvalidDataError),
(2, ErrorKind::SpatialFilterInvalidRadiusError),
(3, ErrorKind::SpatialFilterInvalidConfigurationError),
(4, ErrorKind::SpatialFilterAllocationError),
];
pub const CROSS_VALIDATE_STATUS_ERRORS: StatusErrors = &[
(1, ErrorKind::CrossValidationInvalidDataError),
(2, ErrorKind::CrossValidationInvalidRadiusError),
(3, ErrorKind::CrossValidationInvalidPowerError),
(4, ErrorKind::CrossValidationInvalidConfigurationError),
(5, ErrorKind::CrossValidationAllocationError),
(6, ErrorKind::CrossValidationInvalidModelError),
(7, ErrorKind::CrossValidationInvalidSampleIndexError),
];
pub const SELECT_STATUS_ERRORS: StatusErrors = &[
(1, ErrorKind::VariogramInvalidModelIdError),
(2, ErrorKind::VariogramInvalidModelError),
(3, ErrorKind::VariogramInvalidRangeError),
(4, ErrorKind::VariogramInvalidThreadCountError),
];
pub const CROSS_VARIOGRAM_STATUS_ERRORS: StatusErrors = &[
(1, ErrorKind::CrossVariogramInsufficientSampleError),
(2, ErrorKind::CrossVariogramInvalidAutoModelError),
(3, ErrorKind::CrossVariogramInvalidCrossModelError),
];
#[cfg(test)]
mod tests {
use super::*;
const ALL_TABLES: &[StatusErrors] = &[
IDW_STATUS_ERRORS,
KRIGING_STATUS_ERRORS,
COKRIGING_STATUS_ERRORS,
FILTER_STATUS_ERRORS,
CROSS_VALIDATE_STATUS_ERRORS,
SELECT_STATUS_ERRORS,
CROSS_VARIOGRAM_STATUS_ERRORS,
];
#[test]
fn hierarchy_follows_the_declared_parents() {
use ErrorKind as K;
assert!(ApbaseError::validation_error("x").is(K::ValueError));
assert!(ApbaseError::validation_error("x").is(K::ApbaseError));
assert!(!ApbaseError::value_error("x").is(K::ApbaseError));
let idw = ApbaseError::from_kind(K::IDWInvalidDataError);
assert!(idw.is(K::InterpolationError) && idw.is(K::NativeExecutionError) && idw.is(K::ApbaseError));
assert!(!idw.is(K::ValueError) && !idw.is(K::SpatialFilterError));
let filter = ApbaseError::from_kind(K::SpatialFilterAllocationError);
assert!(filter.is(K::SpatialFilterError) && filter.is(K::NativeExecutionError));
let cv = ApbaseError::from_kind(K::CrossValidationInvalidSampleIndexError);
assert!(cv.is(K::CrossValidationError) && !cv.is(K::InterpolationError));
let vg = ApbaseError::from_kind(K::VariogramInvalidRangeError);
assert!(vg.is(K::VariogramError) && vg.is(K::NativeExecutionError));
let cross = ApbaseError::from_kind(K::CrossVariogramInvalidCrossModelError);
assert!(cross.is(K::CrossVariogramError) && cross.is(K::NativeExecutionError));
assert!(!ApbaseError::new(K::GEOSException, "x").is(K::ApbaseError));
}
#[test]
fn every_status_class_has_its_fixed_message() {
for table in ALL_TABLES {
for (code, kind) in table.iter() {
let error = raise_status_error(*code, table, "fallback");
assert_eq!(error.kind(), *kind);
assert_eq!(error.message(), kind.fixed_message().unwrap());
assert!(error.is(ErrorKind::NativeExecutionError));
}
}
}
#[test]
fn unknown_status_uses_fallback_message() {
let error = raise_status_error(42, IDW_STATUS_ERRORS, "native IDW execution failed");
assert_eq!(error.kind(), ErrorKind::NativeExecutionError);
assert_eq!(error.message(), "native IDW execution failed (status 42)");
}
#[test]
fn display_is_the_message_and_debug_names_the_class() {
let error = ApbaseError::from_kind(ErrorKind::KrigingInvalidRadiusError);
assert_eq!(error.to_string(), "invalid radius");
assert_eq!(
format!("{error:?}"),
"KrigingInvalidRadiusError(\"invalid radius\")"
);
assert_eq!(ErrorKind::IDWAllocationError.name(), "IDWAllocationError");
}
}