use std::fmt;
#[derive(Debug, thiserror::Error)]
pub enum CoreError {
#[error("quota exceeded: limit={limit}, requested={requested}, resource={resource}")]
QuotaExceeded {
resource: &'static str,
limit: u64,
requested: u64,
},
#[error("resource not found: id={id}, type={resource_type}")]
ResourceNotFound {
id: u64,
resource_type: &'static str,
},
#[error("resource already exists: id={id}, type={resource_type}")]
ResourceAlreadyExists {
id: u64,
resource_type: &'static str,
},
#[error("resource poisoned: id={id}, reason={reason}")]
ResourcePoisoned {
id: u64,
reason: String,
},
#[error("ownership violation: expected={expected}, actual={actual}")]
OwnershipViolation {
expected: &'static str,
actual: &'static str,
},
#[error("invalid address range: addr={addr:x}, len={len}, max={max:x}")]
InvalidAddressRange {
addr: u64,
len: u64,
max: u64,
},
#[error("arithmetic overflow: operation={op}, a={a}, b={b}")]
ArithmeticOverflow {
op: &'static str,
a: u64,
b: u64,
},
#[error("state conflict: current={current}, expected={expected}")]
StateConflict {
current: String,
expected: String,
},
#[error("invalid config: {field} — {reason}")]
InvalidConfig {
field: &'static str,
reason: &'static str,
},
#[error("internal error: {0}")]
Internal(String),
#[error("unknown error: {0}")]
Unknown(String),
}
pub type CoreResult<T> = Result<T, CoreError>;
impl CoreError {
pub fn quota_exceeded(resource: &'static str, limit: u64, requested: u64) -> Self {
CoreError::QuotaExceeded {
resource,
limit,
requested,
}
}
pub fn resource_not_found(id: u64, resource_type: &'static str) -> Self {
CoreError::ResourceNotFound { id, resource_type }
}
pub fn resource_already_exists(id: u64, resource_type: &'static str) -> Self {
CoreError::ResourceAlreadyExists {
id,
resource_type,
}
}
pub fn resource_poisoned(id: u64, reason: impl Into<String>) -> Self {
CoreError::ResourcePoisoned {
id,
reason: reason.into(),
}
}
pub fn ownership_violation(expected: &'static str, actual: &'static str) -> Self {
CoreError::OwnershipViolation { expected, actual }
}
pub fn invalid_address_range(addr: u64, len: u64, max: u64) -> Self {
CoreError::InvalidAddressRange { addr, len, max }
}
pub fn arithmetic_overflow(op: &'static str, a: u64, b: u64) -> Self {
CoreError::ArithmeticOverflow { op, a, b }
}
pub fn state_conflict(current: impl Into<String>, expected: impl Into<String>) -> Self {
CoreError::StateConflict {
current: current.into(),
expected: expected.into(),
}
}
pub fn invalid_config(field: &'static str, reason: &'static str) -> Self {
CoreError::InvalidConfig { field, reason }
}
pub fn internal(msg: impl Into<String>) -> Self {
CoreError::Internal(msg.into())
}
pub fn unknown(msg: impl Into<String>) -> Self {
CoreError::Unknown(msg.into())
}
}
pub trait LoggableError: fmt::Display {
fn is_recoverable(&self) -> bool;
fn is_security_related(&self) -> bool;
fn severity(&self) -> ErrorSeverity;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorSeverity {
Info,
Warning,
Error,
Critical,
}
impl LoggableError for CoreError {
fn is_recoverable(&self) -> bool {
matches!(
self,
CoreError::QuotaExceeded { .. }
| CoreError::ResourceNotFound { .. }
| CoreError::ResourceAlreadyExists { .. }
)
}
fn is_security_related(&self) -> bool {
matches!(
self,
CoreError::OwnershipViolation { .. } | CoreError::InvalidAddressRange { .. }
)
}
fn severity(&self) -> ErrorSeverity {
match self {
CoreError::OwnershipViolation { .. } => ErrorSeverity::Critical,
CoreError::ResourcePoisoned { .. } => ErrorSeverity::Critical,
CoreError::InvalidAddressRange { .. } => ErrorSeverity::Error,
CoreError::ArithmeticOverflow { .. } => ErrorSeverity::Error,
CoreError::StateConflict { .. } => ErrorSeverity::Warning,
CoreError::InvalidConfig { .. } => ErrorSeverity::Warning,
CoreError::QuotaExceeded { .. } => ErrorSeverity::Warning,
CoreError::ResourceAlreadyExists { .. } => ErrorSeverity::Warning,
CoreError::ResourceNotFound { .. } => ErrorSeverity::Info,
CoreError::Internal(_) => ErrorSeverity::Error,
CoreError::Unknown(_) => ErrorSeverity::Error,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quota_exceeded_error() {
let err = CoreError::quota_exceeded("frame", 1024, 2048);
assert!(err.is_recoverable());
assert!(!err.is_security_related());
assert_eq!(err.severity(), ErrorSeverity::Warning);
assert!(err.to_string().contains("frame"));
}
#[test]
fn test_ownership_violation_error() {
let err = CoreError::ownership_violation("pool", "other");
assert!(!err.is_recoverable());
assert!(err.is_security_related());
assert_eq!(err.severity(), ErrorSeverity::Critical);
}
#[test]
fn test_invalid_address_range_error() {
let err = CoreError::invalid_address_range(0x1000, 256, 0x2000);
assert!(!err.is_recoverable());
assert!(err.is_security_related());
assert_eq!(err.severity(), ErrorSeverity::Error);
}
#[test]
fn test_arithmetic_overflow_error() {
let err = CoreError::arithmetic_overflow("add", u64::MAX, 1);
assert_eq!(err.severity(), ErrorSeverity::Error);
}
#[test]
fn test_state_conflict_error() {
let err = CoreError::state_conflict("active", "idle");
assert_eq!(err.severity(), ErrorSeverity::Warning);
}
#[test]
fn test_internal_error() {
let err = CoreError::internal("something went wrong");
assert_eq!(err.severity(), ErrorSeverity::Error);
}
#[test]
fn test_quota_exceeded_display() {
let err = CoreError::quota_exceeded("memory", 1024, 2048);
let msg = err.to_string();
assert!(msg.contains("quota exceeded"));
assert!(msg.contains("memory"));
assert!(msg.contains("1024"));
assert!(msg.contains("2048"));
}
#[test]
fn test_resource_not_found_constructor_and_display() {
let err = CoreError::resource_not_found(42, "frame");
assert!(err.is_recoverable());
assert!(!err.is_security_related());
assert_eq!(err.severity(), ErrorSeverity::Info);
let msg = err.to_string();
assert!(msg.contains("resource not found"));
assert!(msg.contains("42"));
assert!(msg.contains("frame"));
}
#[test]
fn test_resource_already_exists_constructor_and_display() {
let err = CoreError::resource_already_exists(7, "connection");
assert!(err.is_recoverable());
assert!(!err.is_security_related());
assert_eq!(err.severity(), ErrorSeverity::Warning);
let msg = err.to_string();
assert!(msg.contains("resource already exists"));
assert!(msg.contains("7"));
assert!(msg.contains("connection"));
}
#[test]
fn test_resource_poisoned_constructor_and_display() {
let err = CoreError::resource_poisoned(100, "corrupted data");
assert!(!err.is_recoverable());
assert!(!err.is_security_related());
assert_eq!(err.severity(), ErrorSeverity::Critical);
let msg = err.to_string();
assert!(msg.contains("resource poisoned"));
assert!(msg.contains("100"));
assert!(msg.contains("corrupted data"));
}
#[test]
fn test_ownership_violation_display() {
let err = CoreError::ownership_violation("pool_a", "pool_b");
let msg = err.to_string();
assert!(msg.contains("ownership violation"));
assert!(msg.contains("pool_a"));
assert!(msg.contains("pool_b"));
}
#[test]
fn test_invalid_address_range_display() {
let err = CoreError::invalid_address_range(0x1000, 256, 0x2000);
let msg = err.to_string();
assert!(msg.contains("invalid address range"));
assert!(msg.contains(&format!("{:x}", 0x1000)));
assert!(msg.contains("256"));
}
#[test]
fn test_arithmetic_overflow_display() {
let err = CoreError::arithmetic_overflow("mul", 100, 200);
let msg = err.to_string();
assert!(msg.contains("arithmetic overflow"));
assert!(msg.contains("mul"));
assert!(msg.contains("100"));
assert!(msg.contains("200"));
}
#[test]
fn test_state_conflict_constructor_and_display() {
let err = CoreError::state_conflict("running", "stopped");
assert!(!err.is_recoverable());
assert!(!err.is_security_related());
assert_eq!(err.severity(), ErrorSeverity::Warning);
let msg = err.to_string();
assert!(msg.contains("state conflict"));
assert!(msg.contains("running"));
assert!(msg.contains("stopped"));
}
#[test]
fn test_internal_error_constructor_and_display() {
let err = CoreError::internal("fatal crash");
assert!(!err.is_recoverable());
assert!(!err.is_security_related());
assert_eq!(err.severity(), ErrorSeverity::Error);
let msg = err.to_string();
assert!(msg.contains("internal error"));
assert!(msg.contains("fatal crash"));
}
#[test]
fn test_unknown_error_constructor_and_display() {
let err = CoreError::unknown("mystery error");
assert!(!err.is_recoverable());
assert!(!err.is_security_related());
assert_eq!(err.severity(), ErrorSeverity::Error);
let msg = err.to_string();
assert!(msg.contains("unknown error"));
assert!(msg.contains("mystery error"));
}
#[test]
fn test_error_severity_equality() {
assert_eq!(ErrorSeverity::Info, ErrorSeverity::Info);
assert_eq!(ErrorSeverity::Warning, ErrorSeverity::Warning);
assert_eq!(ErrorSeverity::Error, ErrorSeverity::Error);
assert_eq!(ErrorSeverity::Critical, ErrorSeverity::Critical);
}
#[test]
fn test_error_severity_clone_copy() {
let s = ErrorSeverity::Warning;
let s2 = s;
assert_eq!(s, s2);
let s3 = s;
assert_eq!(s, s3);
}
#[test]
fn test_error_severity_debug() {
let s = format!("{:?}", ErrorSeverity::Critical);
assert_eq!(s, "Critical");
}
#[test]
fn test_all_recoverable_errors() {
assert!(CoreError::quota_exceeded("mem", 0, 0).is_recoverable());
assert!(CoreError::resource_not_found(0, "x").is_recoverable());
assert!(CoreError::resource_already_exists(0, "x").is_recoverable());
assert!(!CoreError::resource_poisoned(0, "x").is_recoverable());
assert!(!CoreError::ownership_violation("a", "b").is_recoverable());
assert!(!CoreError::invalid_address_range(0, 0, 0).is_recoverable());
assert!(!CoreError::arithmetic_overflow("add", 0, 0).is_recoverable());
assert!(!CoreError::state_conflict("a", "b").is_recoverable());
assert!(!CoreError::internal("x").is_recoverable());
assert!(!CoreError::unknown("x").is_recoverable());
}
#[test]
fn test_all_security_related_errors() {
assert!(CoreError::ownership_violation("a", "b").is_security_related());
assert!(CoreError::invalid_address_range(0, 0, 0).is_security_related());
assert!(!CoreError::quota_exceeded("mem", 0, 0).is_security_related());
assert!(!CoreError::resource_not_found(0, "x").is_security_related());
assert!(!CoreError::resource_already_exists(0, "x").is_security_related());
assert!(!CoreError::resource_poisoned(0, "x").is_security_related());
assert!(!CoreError::arithmetic_overflow("add", 0, 0).is_security_related());
assert!(!CoreError::state_conflict("a", "b").is_security_related());
assert!(!CoreError::internal("x").is_security_related());
assert!(!CoreError::unknown("x").is_security_related());
}
#[test]
fn test_loggable_error_trait_object() {
let err: Box<dyn LoggableError> = Box::new(CoreError::internal("test"));
assert!(!err.is_recoverable());
assert!(!err.is_security_related());
assert_eq!(err.severity(), ErrorSeverity::Error);
assert!(err.to_string().contains("internal error"));
}
}