use thiserror::Error;
use std::fmt;
#[derive(Error, Debug)]
pub enum AnvilKitError {
#[error("渲染错误: {message}")]
Render {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("物理错误: {message}")]
Physics {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("资源错误: {message}")]
Asset {
message: String,
path: Option<String>,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("音频错误: {message}")]
Audio {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("输入错误: {message}")]
Input {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("ECS 错误: {message}")]
Ecs {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("窗口错误: {message}")]
Window {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("配置错误: {message}")]
Config {
message: String,
key: Option<String>,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("网络错误: {message}")]
Network {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("I/O 错误: {0}")]
Io(#[from] std::io::Error),
#[error("序列化错误: {message}")]
Serialization {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
#[error("AnvilKit 错误: {message}")]
Generic {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ErrorCategory {
Render,
Physics,
Asset,
Audio,
Input,
Ecs,
Window,
Config,
Network,
Io,
Serialization,
Generic,
}
impl fmt::Display for ErrorCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
ErrorCategory::Render => "渲染",
ErrorCategory::Physics => "物理",
ErrorCategory::Asset => "资源",
ErrorCategory::Audio => "音频",
ErrorCategory::Input => "输入",
ErrorCategory::Ecs => "ECS",
ErrorCategory::Window => "窗口",
ErrorCategory::Config => "配置",
ErrorCategory::Network => "网络",
ErrorCategory::Io => "I/O",
ErrorCategory::Serialization => "序列化",
ErrorCategory::Generic => "通用",
};
write!(f, "{}", name)
}
}
impl AnvilKitError {
pub fn render(message: impl Into<String>) -> Self {
Self::Render {
message: message.into(),
source: None,
}
}
pub fn render_with_source(
message: impl Into<String>,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::Render {
message: message.into(),
source: Some(Box::new(source)),
}
}
pub fn physics(message: impl Into<String>) -> Self {
Self::Physics {
message: message.into(),
source: None,
}
}
pub fn physics_with_source(
message: impl Into<String>,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self::Physics {
message: message.into(),
source: Some(Box::new(source)),
}
}
pub fn asset(message: impl Into<String>) -> Self {
Self::Asset {
message: message.into(),
path: None,
source: None,
}
}
pub fn asset_with_path(message: impl Into<String>, path: impl Into<String>) -> Self {
Self::Asset {
message: message.into(),
path: Some(path.into()),
source: None,
}
}
pub fn audio(message: impl Into<String>) -> Self {
Self::Audio {
message: message.into(),
source: None,
}
}
pub fn input(message: impl Into<String>) -> Self {
Self::Input {
message: message.into(),
source: None,
}
}
pub fn ecs(message: impl Into<String>) -> Self {
Self::Ecs {
message: message.into(),
source: None,
}
}
pub fn window(message: impl Into<String>) -> Self {
Self::Window {
message: message.into(),
source: None,
}
}
pub fn config(message: impl Into<String>) -> Self {
Self::Config {
message: message.into(),
key: None,
source: None,
}
}
pub fn config_with_key(message: impl Into<String>, key: impl Into<String>) -> Self {
Self::Config {
message: message.into(),
key: Some(key.into()),
source: None,
}
}
pub fn network(message: impl Into<String>) -> Self {
Self::Network {
message: message.into(),
source: None,
}
}
pub fn serialization(message: impl Into<String>) -> Self {
Self::Serialization {
message: message.into(),
source: None,
}
}
pub fn generic(message: impl Into<String>) -> Self {
Self::Generic {
message: message.into(),
source: None,
}
}
pub fn category(&self) -> ErrorCategory {
match self {
Self::Render { .. } => ErrorCategory::Render,
Self::Physics { .. } => ErrorCategory::Physics,
Self::Asset { .. } => ErrorCategory::Asset,
Self::Audio { .. } => ErrorCategory::Audio,
Self::Input { .. } => ErrorCategory::Input,
Self::Ecs { .. } => ErrorCategory::Ecs,
Self::Window { .. } => ErrorCategory::Window,
Self::Config { .. } => ErrorCategory::Config,
Self::Network { .. } => ErrorCategory::Network,
Self::Io(_) => ErrorCategory::Io,
Self::Serialization { .. } => ErrorCategory::Serialization,
Self::Generic { .. } => ErrorCategory::Generic,
}
}
pub fn message(&self) -> String {
match self {
Self::Render { message, .. } => message.clone(),
Self::Physics { message, .. } => message.clone(),
Self::Asset { message, .. } => message.clone(),
Self::Audio { message, .. } => message.clone(),
Self::Input { message, .. } => message.clone(),
Self::Ecs { message, .. } => message.clone(),
Self::Window { message, .. } => message.clone(),
Self::Config { message, .. } => message.clone(),
Self::Network { message, .. } => message.clone(),
Self::Io(err) => err.to_string(),
Self::Serialization { message, .. } => message.clone(),
Self::Generic { message, .. } => message.clone(),
}
}
pub fn is_category(&self, category: ErrorCategory) -> bool {
self.category() == category
}
pub fn with_context(self, context: impl Into<String>) -> Self {
let context = context.into();
match self {
Self::Generic { message, source } => Self::Generic {
message: format!("{}: {}", context, message),
source,
},
_ => Self::Generic {
message: format!("{}: {}", context, self),
source: Some(Box::new(self)),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_creation() {
let error = AnvilKitError::render("测试渲染错误");
assert_eq!(error.category(), ErrorCategory::Render);
assert_eq!(error.message(), "测试渲染错误");
assert!(error.is_category(ErrorCategory::Render));
}
#[test]
fn test_error_with_path() {
let error = AnvilKitError::asset_with_path("加载失败", "texture.png");
if let AnvilKitError::Asset { path, .. } = &error {
assert_eq!(path.as_ref().unwrap(), "texture.png");
} else {
panic!("Expected Asset error");
}
}
#[test]
fn test_error_with_context() {
let original = AnvilKitError::render("着色器编译失败");
let with_context = original.with_context("初始化渲染器时");
assert!(with_context.to_string().contains("初始化渲染器时"));
assert!(with_context.to_string().contains("着色器编译失败"));
}
#[test]
fn test_io_error_conversion() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "文件未找到");
let anvilkit_error: AnvilKitError = io_error.into();
assert_eq!(anvilkit_error.category(), ErrorCategory::Io);
}
#[test]
fn test_error_category_display() {
assert_eq!(ErrorCategory::Render.to_string(), "渲染");
assert_eq!(ErrorCategory::Physics.to_string(), "物理");
assert_eq!(ErrorCategory::Asset.to_string(), "资源");
}
#[test]
fn test_error_with_source() {
let source_error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "权限不足");
let error = AnvilKitError::render_with_source("渲染初始化失败", source_error);
assert!(std::error::Error::source(&error).is_some());
assert_eq!(error.category(), ErrorCategory::Render);
}
#[test]
fn test_all_error_variants() {
let errors = vec![
(AnvilKitError::render("test"), ErrorCategory::Render),
(AnvilKitError::physics("test"), ErrorCategory::Physics),
(AnvilKitError::asset("test"), ErrorCategory::Asset),
(AnvilKitError::audio("test"), ErrorCategory::Audio),
(AnvilKitError::input("test"), ErrorCategory::Input),
(AnvilKitError::ecs("test"), ErrorCategory::Ecs),
(AnvilKitError::window("test"), ErrorCategory::Window),
(AnvilKitError::config("test"), ErrorCategory::Config),
(AnvilKitError::network("test"), ErrorCategory::Network),
(AnvilKitError::serialization("test"), ErrorCategory::Serialization),
(AnvilKitError::generic("test"), ErrorCategory::Generic),
];
for (error, expected_category) in errors {
assert_eq!(error.category(), expected_category);
assert_eq!(error.message(), "test");
assert!(error.is_category(expected_category));
}
}
#[test]
fn test_error_display_format() {
let error = AnvilKitError::render("GPU 初始化失败");
let display = format!("{}", error);
assert!(display.contains("渲染错误"));
assert!(display.contains("GPU 初始化失败"));
}
#[test]
fn test_error_debug_format() {
let error = AnvilKitError::physics("碰撞检测溢出");
let debug = format!("{:?}", error);
assert!(debug.contains("Physics"));
}
#[test]
fn test_config_error_with_key() {
let error = AnvilKitError::config_with_key("无效的分辨率", "window.resolution");
if let AnvilKitError::Config { key, message, .. } = &error {
assert_eq!(key.as_ref().unwrap(), "window.resolution");
assert_eq!(message, "无效的分辨率");
} else {
panic!("Expected Config error");
}
}
#[test]
fn test_error_context_chaining() {
let original = AnvilKitError::asset("纹理加载失败");
let with_ctx = original.with_context("初始化场景时");
assert_eq!(with_ctx.category(), ErrorCategory::Generic);
assert!(std::error::Error::source(&with_ctx).is_some());
}
#[test]
fn test_error_generic_context_chaining() {
let original = AnvilKitError::generic("底层错误");
let with_ctx = original.with_context("上层调用");
assert_eq!(with_ctx.category(), ErrorCategory::Generic);
assert!(with_ctx.message().contains("上层调用"));
assert!(with_ctx.message().contains("底层错误"));
}
#[test]
fn test_all_category_display() {
let categories = vec![
(ErrorCategory::Render, "渲染"),
(ErrorCategory::Physics, "物理"),
(ErrorCategory::Asset, "资源"),
(ErrorCategory::Audio, "音频"),
(ErrorCategory::Input, "输入"),
(ErrorCategory::Ecs, "ECS"),
(ErrorCategory::Window, "窗口"),
(ErrorCategory::Config, "配置"),
(ErrorCategory::Network, "网络"),
(ErrorCategory::Io, "I/O"),
(ErrorCategory::Serialization, "序列化"),
(ErrorCategory::Generic, "通用"),
];
for (category, expected_display) in categories {
assert_eq!(category.to_string(), expected_display);
}
}
#[test]
fn test_error_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<AnvilKitError>();
}
}