1use thiserror::Error;
6use std::fmt;
7
8#[derive(Error, Debug)]
20pub enum AnvilKitError {
21 #[error("渲染错误: {message}")]
25 Render {
26 message: String,
28 #[source]
30 source: Option<Box<dyn std::error::Error + Send + Sync>>,
31 },
32
33 #[error("物理错误: {message}")]
37 Physics {
38 message: String,
40 #[source]
42 source: Option<Box<dyn std::error::Error + Send + Sync>>,
43 },
44
45 #[error("资源错误: {message}")]
49 Asset {
50 message: String,
52 path: Option<String>,
54 #[source]
56 source: Option<Box<dyn std::error::Error + Send + Sync>>,
57 },
58
59 #[error("音频错误: {message}")]
63 Audio {
64 message: String,
66 #[source]
68 source: Option<Box<dyn std::error::Error + Send + Sync>>,
69 },
70
71 #[error("输入错误: {message}")]
75 Input {
76 message: String,
78 #[source]
80 source: Option<Box<dyn std::error::Error + Send + Sync>>,
81 },
82
83 #[error("ECS 错误: {message}")]
87 Ecs {
88 message: String,
90 #[source]
92 source: Option<Box<dyn std::error::Error + Send + Sync>>,
93 },
94
95 #[error("窗口错误: {message}")]
99 Window {
100 message: String,
102 #[source]
104 source: Option<Box<dyn std::error::Error + Send + Sync>>,
105 },
106
107 #[error("配置错误: {message}")]
111 Config {
112 message: String,
114 key: Option<String>,
116 #[source]
118 source: Option<Box<dyn std::error::Error + Send + Sync>>,
119 },
120
121 #[error("网络错误: {message}")]
125 Network {
126 message: String,
128 #[source]
130 source: Option<Box<dyn std::error::Error + Send + Sync>>,
131 },
132
133 #[error("I/O 错误: {0}")]
137 Io(#[from] std::io::Error),
138
139 #[error("序列化错误: {message}")]
141 Serialization {
142 message: String,
144 #[source]
146 source: Option<Box<dyn std::error::Error + Send + Sync>>,
147 },
148
149 #[error("AnvilKit 错误: {message}")]
153 Generic {
154 message: String,
156 #[source]
158 source: Option<Box<dyn std::error::Error + Send + Sync>>,
159 },
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
166pub enum ErrorCategory {
167 Render,
169 Physics,
171 Asset,
173 Audio,
175 Input,
177 Ecs,
179 Window,
181 Config,
183 Network,
185 Io,
187 Serialization,
189 Generic,
191}
192
193impl fmt::Display for ErrorCategory {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 let name = match self {
196 ErrorCategory::Render => "渲染",
197 ErrorCategory::Physics => "物理",
198 ErrorCategory::Asset => "资源",
199 ErrorCategory::Audio => "音频",
200 ErrorCategory::Input => "输入",
201 ErrorCategory::Ecs => "ECS",
202 ErrorCategory::Window => "窗口",
203 ErrorCategory::Config => "配置",
204 ErrorCategory::Network => "网络",
205 ErrorCategory::Io => "I/O",
206 ErrorCategory::Serialization => "序列化",
207 ErrorCategory::Generic => "通用",
208 };
209 write!(f, "{}", name)
210 }
211}
212
213impl AnvilKitError {
214 pub fn render(message: impl Into<String>) -> Self {
224 Self::Render {
225 message: message.into(),
226 source: None,
227 }
228 }
229
230 pub fn render_with_source(
232 message: impl Into<String>,
233 source: impl std::error::Error + Send + Sync + 'static,
234 ) -> Self {
235 Self::Render {
236 message: message.into(),
237 source: Some(Box::new(source)),
238 }
239 }
240
241 pub fn physics(message: impl Into<String>) -> Self {
243 Self::Physics {
244 message: message.into(),
245 source: None,
246 }
247 }
248
249 pub fn physics_with_source(
251 message: impl Into<String>,
252 source: impl std::error::Error + Send + Sync + 'static,
253 ) -> Self {
254 Self::Physics {
255 message: message.into(),
256 source: Some(Box::new(source)),
257 }
258 }
259
260 pub fn asset(message: impl Into<String>) -> Self {
262 Self::Asset {
263 message: message.into(),
264 path: None,
265 source: None,
266 }
267 }
268
269 pub fn asset_with_path(message: impl Into<String>, path: impl Into<String>) -> Self {
271 Self::Asset {
272 message: message.into(),
273 path: Some(path.into()),
274 source: None,
275 }
276 }
277
278 pub fn audio(message: impl Into<String>) -> Self {
280 Self::Audio {
281 message: message.into(),
282 source: None,
283 }
284 }
285
286 pub fn input(message: impl Into<String>) -> Self {
288 Self::Input {
289 message: message.into(),
290 source: None,
291 }
292 }
293
294 pub fn ecs(message: impl Into<String>) -> Self {
296 Self::Ecs {
297 message: message.into(),
298 source: None,
299 }
300 }
301
302 pub fn window(message: impl Into<String>) -> Self {
304 Self::Window {
305 message: message.into(),
306 source: None,
307 }
308 }
309
310 pub fn config(message: impl Into<String>) -> Self {
312 Self::Config {
313 message: message.into(),
314 key: None,
315 source: None,
316 }
317 }
318
319 pub fn config_with_key(message: impl Into<String>, key: impl Into<String>) -> Self {
321 Self::Config {
322 message: message.into(),
323 key: Some(key.into()),
324 source: None,
325 }
326 }
327
328 pub fn network(message: impl Into<String>) -> Self {
330 Self::Network {
331 message: message.into(),
332 source: None,
333 }
334 }
335
336 pub fn serialization(message: impl Into<String>) -> Self {
338 Self::Serialization {
339 message: message.into(),
340 source: None,
341 }
342 }
343
344 pub fn generic(message: impl Into<String>) -> Self {
346 Self::Generic {
347 message: message.into(),
348 source: None,
349 }
350 }
351
352 pub fn category(&self) -> ErrorCategory {
363 match self {
364 Self::Render { .. } => ErrorCategory::Render,
365 Self::Physics { .. } => ErrorCategory::Physics,
366 Self::Asset { .. } => ErrorCategory::Asset,
367 Self::Audio { .. } => ErrorCategory::Audio,
368 Self::Input { .. } => ErrorCategory::Input,
369 Self::Ecs { .. } => ErrorCategory::Ecs,
370 Self::Window { .. } => ErrorCategory::Window,
371 Self::Config { .. } => ErrorCategory::Config,
372 Self::Network { .. } => ErrorCategory::Network,
373 Self::Io(_) => ErrorCategory::Io,
374 Self::Serialization { .. } => ErrorCategory::Serialization,
375 Self::Generic { .. } => ErrorCategory::Generic,
376 }
377 }
378
379 pub fn message(&self) -> String {
383 match self {
384 Self::Render { message, .. } => message.clone(),
385 Self::Physics { message, .. } => message.clone(),
386 Self::Asset { message, .. } => message.clone(),
387 Self::Audio { message, .. } => message.clone(),
388 Self::Input { message, .. } => message.clone(),
389 Self::Ecs { message, .. } => message.clone(),
390 Self::Window { message, .. } => message.clone(),
391 Self::Config { message, .. } => message.clone(),
392 Self::Network { message, .. } => message.clone(),
393 Self::Io(err) => err.to_string(),
394 Self::Serialization { message, .. } => message.clone(),
395 Self::Generic { message, .. } => message.clone(),
396 }
397 }
398
399 pub fn is_category(&self, category: ErrorCategory) -> bool {
411 self.category() == category
412 }
413
414 pub fn with_context(self, context: impl Into<String>) -> Self {
418 let context = context.into();
419 match self {
420 Self::Generic { message, source } => Self::Generic {
421 message: format!("{}: {}", context, message),
422 source,
423 },
424 _ => Self::Generic {
425 message: format!("{}: {}", context, self),
426 source: Some(Box::new(self)),
427 },
428 }
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 #[test]
437 fn test_error_creation() {
438 let error = AnvilKitError::render("测试渲染错误");
439 assert_eq!(error.category(), ErrorCategory::Render);
440 assert_eq!(error.message(), "测试渲染错误");
441 assert!(error.is_category(ErrorCategory::Render));
442 }
443
444 #[test]
445 fn test_error_with_path() {
446 let error = AnvilKitError::asset_with_path("加载失败", "texture.png");
447 if let AnvilKitError::Asset { path, .. } = &error {
448 assert_eq!(path.as_ref().unwrap(), "texture.png");
449 } else {
450 panic!("Expected Asset error");
451 }
452 }
453
454 #[test]
455 fn test_error_with_context() {
456 let original = AnvilKitError::render("着色器编译失败");
457 let with_context = original.with_context("初始化渲染器时");
458
459 assert!(with_context.to_string().contains("初始化渲染器时"));
460 assert!(with_context.to_string().contains("着色器编译失败"));
461 }
462
463 #[test]
464 fn test_io_error_conversion() {
465 let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "文件未找到");
466 let anvilkit_error: AnvilKitError = io_error.into();
467
468 assert_eq!(anvilkit_error.category(), ErrorCategory::Io);
469 }
470
471 #[test]
472 fn test_error_category_display() {
473 assert_eq!(ErrorCategory::Render.to_string(), "渲染");
474 assert_eq!(ErrorCategory::Physics.to_string(), "物理");
475 assert_eq!(ErrorCategory::Asset.to_string(), "资源");
476 }
477
478 #[test]
479 fn test_error_with_source() {
480 let source_error = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "权限不足");
481 let error = AnvilKitError::render_with_source("渲染初始化失败", source_error);
482
483 assert!(std::error::Error::source(&error).is_some());
484 assert_eq!(error.category(), ErrorCategory::Render);
485 }
486
487 #[test]
488 fn test_all_error_variants() {
489 let errors = vec![
491 (AnvilKitError::render("test"), ErrorCategory::Render),
492 (AnvilKitError::physics("test"), ErrorCategory::Physics),
493 (AnvilKitError::asset("test"), ErrorCategory::Asset),
494 (AnvilKitError::audio("test"), ErrorCategory::Audio),
495 (AnvilKitError::input("test"), ErrorCategory::Input),
496 (AnvilKitError::ecs("test"), ErrorCategory::Ecs),
497 (AnvilKitError::window("test"), ErrorCategory::Window),
498 (AnvilKitError::config("test"), ErrorCategory::Config),
499 (AnvilKitError::network("test"), ErrorCategory::Network),
500 (AnvilKitError::serialization("test"), ErrorCategory::Serialization),
501 (AnvilKitError::generic("test"), ErrorCategory::Generic),
502 ];
503
504 for (error, expected_category) in errors {
505 assert_eq!(error.category(), expected_category);
506 assert_eq!(error.message(), "test");
507 assert!(error.is_category(expected_category));
508 }
509 }
510
511 #[test]
512 fn test_error_display_format() {
513 let error = AnvilKitError::render("GPU 初始化失败");
514 let display = format!("{}", error);
515 assert!(display.contains("渲染错误"));
516 assert!(display.contains("GPU 初始化失败"));
517 }
518
519 #[test]
520 fn test_error_debug_format() {
521 let error = AnvilKitError::physics("碰撞检测溢出");
522 let debug = format!("{:?}", error);
523 assert!(debug.contains("Physics"));
524 }
525
526 #[test]
527 fn test_config_error_with_key() {
528 let error = AnvilKitError::config_with_key("无效的分辨率", "window.resolution");
529 if let AnvilKitError::Config { key, message, .. } = &error {
530 assert_eq!(key.as_ref().unwrap(), "window.resolution");
531 assert_eq!(message, "无效的分辨率");
532 } else {
533 panic!("Expected Config error");
534 }
535 }
536
537 #[test]
538 fn test_error_context_chaining() {
539 let original = AnvilKitError::asset("纹理加载失败");
540 let with_ctx = original.with_context("初始化场景时");
541
542 assert_eq!(with_ctx.category(), ErrorCategory::Generic);
544 assert!(std::error::Error::source(&with_ctx).is_some());
545 }
546
547 #[test]
548 fn test_error_generic_context_chaining() {
549 let original = AnvilKitError::generic("底层错误");
551 let with_ctx = original.with_context("上层调用");
552
553 assert_eq!(with_ctx.category(), ErrorCategory::Generic);
554 assert!(with_ctx.message().contains("上层调用"));
555 assert!(with_ctx.message().contains("底层错误"));
556 }
557
558 #[test]
559 fn test_all_category_display() {
560 let categories = vec![
561 (ErrorCategory::Render, "渲染"),
562 (ErrorCategory::Physics, "物理"),
563 (ErrorCategory::Asset, "资源"),
564 (ErrorCategory::Audio, "音频"),
565 (ErrorCategory::Input, "输入"),
566 (ErrorCategory::Ecs, "ECS"),
567 (ErrorCategory::Window, "窗口"),
568 (ErrorCategory::Config, "配置"),
569 (ErrorCategory::Network, "网络"),
570 (ErrorCategory::Io, "I/O"),
571 (ErrorCategory::Serialization, "序列化"),
572 (ErrorCategory::Generic, "通用"),
573 ];
574
575 for (category, expected_display) in categories {
576 assert_eq!(category.to_string(), expected_display);
577 }
578 }
579
580 #[test]
581 fn test_error_is_send_sync() {
582 fn assert_send_sync<T: Send + Sync>() {}
583 assert_send_sync::<AnvilKitError>();
584 }
585}