use thiserror::Error;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HandleKind {
Body,
Shape,
Joint,
Contact,
DynamicTreeProxy,
ReplayWorld,
}
impl std::fmt::Display for HandleKind {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(match self {
Self::Body => "body",
Self::Shape => "shape",
Self::Joint => "joint",
Self::Contact => "contact",
Self::DynamicTreeProxy => "dynamic-tree proxy",
Self::ReplayWorld => "replay world",
})
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InvalidValueReason {
NonFinite,
OutOfRange,
InteriorNul,
InvalidCombination,
Malformed,
}
impl std::fmt::Display for InvalidValueReason {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(match self {
Self::NonFinite => "value must be finite",
Self::OutOfRange => "value is outside the accepted range",
Self::InteriorNul => "string contains an interior NUL byte",
Self::InvalidCombination => "values form an invalid combination",
Self::Malformed => "value is malformed",
})
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[non_exhaustive]
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum Error {
#[error("invalid value for {context}: {reason}")]
InvalidValue {
context: &'static str,
reason: InvalidValueReason,
},
#[error("foreign {kind} handle")]
ForeignHandle {
kind: HandleKind,
},
#[error("stale {kind} handle")]
StaleHandle {
kind: HandleKind,
},
#[error("boxddd API called from a Box3D callback; Box3D world is locked")]
InCallback,
#[error("native Box3D operation failed")]
NativeFailure,
#[error("failed to reserve Rust bookkeeping storage")]
AllocationFailed,
#[error("failed to load or save a Box3D recording")]
RecordingIoFailed,
#[error("Box3D recording is already in use by a world")]
RecordingInUse,
#[error("this API is not supported on the current WASM target")]
UnsupportedOnWasm,
#[error("Rust callback panicked and native traversal was stopped")]
CallbackPanicked,
#[error("no callback slot is available")]
CallbackSlotsExhausted,
#[error("provenance token space is exhausted")]
ProvenanceExhausted,
#[error("provider callback bridge failed")]
ProviderCallbackFailed,
}
#[cfg(test)]
mod tests {
use super::{Error, HandleKind, InvalidValueReason};
#[test]
fn error_is_thread_safe_and_standard() {
fn assert_error<T: std::error::Error + Send + Sync + 'static>() {}
assert_error::<Error>();
}
#[test]
fn canonical_errors_preserve_recovery_categories() {
let invalid = Error::InvalidValue {
context: "body.linear_damping",
reason: InvalidValueReason::OutOfRange,
};
let foreign = Error::ForeignHandle {
kind: HandleKind::Body,
};
let stale = Error::StaleHandle {
kind: HandleKind::Body,
};
assert!(invalid.to_string().contains("body.linear_damping"));
assert_ne!(foreign, stale);
assert_eq!(
Error::NativeFailure.to_string(),
"native Box3D operation failed"
);
}
}