use crate::compat::{fmt, format, Box, MiniToString, String};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct ErrorId(pub i32);
impl ErrorId {
pub const SUCCESS: Self = Self(0);
pub const NOT_IMPLEMENTED: Self = Self(1);
pub const UNSUPPORTED_OPERATION: Self = Self(2);
pub const INVALID_ARGUMENT: Self = Self(3);
pub const GENERAL: Self = Self(999);
pub const NULL_POINTER: Self = Self(4);
pub const OUT_OF_MEMORY: Self = Self(5);
pub const LOCK_POISONED: Self = Self(6);
pub const WIDGET_BASE_NOT_IMPL: Self = Self(100);
pub const WIDGET_NOT_FOUND: Self = Self(101);
pub const WIDGET_INVALID_STATE: Self = Self(102);
pub const WIDGET_DEPRECATED: Self = Self(103);
pub const PLATFORM_UNSUPPORTED: Self = Self(200);
pub const PLATFORM_INIT_FAILED: Self = Self(201);
pub const CLIPBOARD_FAILED: Self = Self(202);
pub const DRAG_DROP_FAILED: Self = Self(203);
pub const RENDER_CONTEXT_INVALID: Self = Self(300);
pub const RENDER_PIPELINE_FAILED: Self = Self(301);
pub const I18N_LOAD_FAILED: Self = Self(400);
pub const FILE_NOT_FOUND: Self = Self(401);
pub const EW_SUCCESS: Self = Self(0);
pub const EW_NOT_IMPLEMENTED: Self = Self(1);
pub const EW_UNSUPPORTED_OPERATION: Self = Self(2);
pub const EW_INVALID_ARGUMENT: Self = Self(3);
pub const EW_GENERAL: Self = Self(999);
pub const EW_NULL_POINTER: Self = Self(4);
pub const EW_OUT_OF_MEMORY: Self = Self(5);
pub const EW_LOCK_POISONED: Self = Self(6);
pub const EW_WIDGET_BASE_NOT_IMPL: Self = Self(100);
pub const EW_WIDGET_NOT_FOUND: Self = Self(101);
pub const EW_WIDGET_INVALID_STATE: Self = Self(102);
pub const EW_WIDGET_DEPRECATED: Self = Self(103);
pub const EW_PLATFORM_UNSUPPORTED: Self = Self(200);
pub const EW_PLATFORM_INIT_FAILED: Self = Self(201);
pub const EW_CLIPBOARD_FAILED: Self = Self(202);
pub const EW_DRAG_DROP_FAILED: Self = Self(203);
pub const EW_RENDER_CONTEXT_INVALID: Self = Self(300);
pub const EW_RENDER_PIPELINE_FAILED: Self = Self(301);
pub const EW_I18N_LOAD_FAILED: Self = Self(400);
pub const EW_FILE_NOT_FOUND: Self = Self(401);
}
#[derive(Debug)]
pub struct RwError {
pub id: ErrorId,
pub message: String,
pub cause: Option<Box<dyn core::error::Error + Send + Sync>>,
}
impl Clone for RwError {
fn clone(&self) -> Self {
Self { id: self.id, message: self.message.clone(), cause: None }
}
}
impl RwError {
pub fn new(id: ErrorId, message: impl Into<String>) -> Self {
Self { id, message: message.into(), cause: None }
}
pub fn with_cause(
id: ErrorId,
message: impl Into<String>,
cause: impl core::error::Error + Send + Sync + 'static,
) -> Self {
Self { id, message: message.into(), cause: Some(Box::new(cause)) }
}
pub fn not_implemented(feature: impl Into<String>) -> Self {
Self::new(ErrorId::NOT_IMPLEMENTED, format!("not implemented: {}", feature.into()))
}
pub fn msg(message: impl Into<String>) -> Self {
Self::new(ErrorId::GENERAL, message)
}
pub fn from_panic(panic_info: &dyn crate::compat::Any) -> Self {
let msg = panic_info
.downcast_ref::<&str>()
.map(|s| s.to_string())
.or_else(|| panic_info.downcast_ref::<String>().cloned())
.unwrap_or_else(|| String::from("unknown panic"));
Self::new(ErrorId::GENERAL, msg)
}
}
impl fmt::Display for RwError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[EW-{:03}] {}", self.id.0, self.message)
}
}
impl core::error::Error for RwError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
self.cause.as_deref().map(|e| e as &(dyn core::error::Error + 'static))
}
}
impl From<crate::core::CoreError> for RwError {
fn from(err: crate::core::CoreError) -> Self {
let (id, message) = match &err {
crate::core::CoreError::InvalidArgument(msg) => {
(ErrorId::INVALID_ARGUMENT, msg.clone())
}
crate::core::CoreError::NotSupported(msg) => {
(ErrorId::UNSUPPORTED_OPERATION, msg.clone())
}
crate::core::CoreError::NotFound(msg) => (ErrorId::FILE_NOT_FOUND, msg.clone()),
crate::core::CoreError::Internal(msg) => (ErrorId::GENERAL, msg.clone()),
};
RwError::with_cause(id, message, err)
}
}
pub type RwResult<T> = Result<T, RwError>;
#[cfg(not(alloc_frugal))]
pub fn catch_panic<F, T>(f: F) -> RwResult<T>
where
F: FnOnce() -> T + core::panic::UnwindSafe,
{
match std::panic::catch_unwind(f) {
Ok(v) => Ok(v),
Err(e) => Err(RwError::from_panic(&*e)),
}
}
#[cfg(alloc_frugal)]
pub fn catch_panic<F, T>(f: F) -> RwResult<T>
where
F: FnOnce() -> T + core::panic::UnwindSafe,
{
Ok(f())
}
pub mod ffi;
pub use ffi::{c_try_fallback, CAbiSafe};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rw_error_display() {
let e = RwError::msg("test error");
assert!(e.to_string().contains("test"));
}
#[test]
fn rw_error_not_implemented() {
let e = RwError::not_implemented("feature");
assert!(e.to_string().contains("not implemented"));
assert!(e.to_string().contains("feature"));
}
#[test]
fn rw_error_msg_not_success() {
let e = RwError::msg("test");
assert_ne!(e.id, ErrorId::SUCCESS);
}
#[test]
fn ew_error_alias_values_match() {
assert_eq!(ErrorId::EW_SUCCESS.0, ErrorId::SUCCESS.0);
assert_eq!(ErrorId::EW_INVALID_ARGUMENT.0, ErrorId::INVALID_ARGUMENT.0);
assert_eq!(ErrorId::EW_FILE_NOT_FOUND.0, ErrorId::FILE_NOT_FOUND.0);
}
#[test]
fn rw_error_display_prefix_uses_ew() {
let err = RwError::new(ErrorId::INVALID_ARGUMENT, "bad input");
assert!(err.to_string().starts_with("[EW-"));
}
#[test]
fn a_converted_core_error_keeps_its_cause_in_the_source_chain() {
use core::error::Error;
let err = RwError::from(crate::core::CoreError::NotSupported("no GPU".to_string()));
assert_eq!(err.id, ErrorId::UNSUPPORTED_OPERATION);
let cause = err.source().expect("the converted CoreError must be the source");
assert!(cause.to_string().contains("no GPU"));
}
#[test]
fn an_error_built_from_an_id_has_no_source() {
use core::error::Error;
assert!(RwError::new(ErrorId::INVALID_ARGUMENT, "x").source().is_none());
}
#[test]
fn an_internal_core_error_round_trips_as_internal() {
let err = RwError::from(crate::core::CoreError::Internal("boom".to_string()));
assert_eq!(err.id, ErrorId::GENERAL, "internal is not \"not implemented\"");
assert!(matches!(crate::core::CoreError::from(err), crate::core::CoreError::Internal(_)));
}
}