1use std::io::{self, ErrorKind};
2use thiserror::Error;
3
4#[derive(Error, Debug)]
6pub enum AppError {
7 #[error("{0} component not exists")]
9 ComponentNotExist(&'static str),
10
11 #[error(transparent)]
13 EnvError(#[from] dotenvy::Error),
14
15 #[error(transparent)]
17 IOError(#[from] io::Error),
18
19 #[error(transparent)]
21 TomlParseError(#[from] toml::de::Error),
22
23 #[error("merge toml error: {0}")]
25 TomlMergeError(String),
26
27 #[error(transparent)]
29 JoinError(#[from] tokio::task::JoinError),
30
31 #[error("Failed to deserialize the configuration of prefix \"{0}\": {1}")]
33 DeserializeErr(&'static str, toml::de::Error),
34
35 #[error(transparent)]
37 OtherError(#[from] anyhow::Error),
38}
39
40impl AppError {
41 pub fn from_io(kind: ErrorKind, msg: &str) -> Self {
43 AppError::IOError(io::Error::new(kind, msg))
44 }
45}
46
47pub type Result<T> = std::result::Result<T, AppError>;
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn test_component_not_exist_error() {
56 let error = AppError::ComponentNotExist("TestComponent");
57 let error_msg = error.to_string();
58 assert!(error_msg.contains("TestComponent"));
59 assert!(error_msg.contains("component not exists"));
60 }
61
62 #[test]
63 fn test_from_io_error() {
64 let error = AppError::from_io(ErrorKind::NotFound, "file not found");
65 match error {
66 AppError::IOError(e) => {
67 assert_eq!(e.kind(), ErrorKind::NotFound);
68 assert!(e.to_string().contains("file not found"));
69 }
70 _ => panic!("Expected IOError"),
71 }
72 }
73
74 #[test]
75 fn test_toml_merge_error() {
76 let error = AppError::TomlMergeError("conflicting keys".to_string());
77 let error_msg = error.to_string();
78 assert!(error_msg.contains("merge toml error"));
79 assert!(error_msg.contains("conflicting keys"));
80 }
81
82 #[test]
83 fn test_deserialize_error() {
84 let toml_err = toml::from_str::<i32>("invalid").unwrap_err();
85 let error = AppError::DeserializeErr("my-config", toml_err);
86 let error_msg = error.to_string();
87 assert!(error_msg.contains("Failed to deserialize"));
88 assert!(error_msg.contains("my-config"));
89 }
90
91 #[test]
92 fn test_io_error_conversion() {
93 let io_error = io::Error::new(ErrorKind::PermissionDenied, "access denied");
94 let app_error: AppError = io_error.into();
95
96 match app_error {
97 AppError::IOError(e) => {
98 assert_eq!(e.kind(), ErrorKind::PermissionDenied);
99 }
100 _ => panic!("Expected IOError"),
101 }
102 }
103
104 #[test]
105 fn test_anyhow_error_conversion() {
106 let anyhow_err = anyhow::anyhow!("something went wrong");
107 let app_error: AppError = anyhow_err.into();
108
109 match app_error {
110 AppError::OtherError(e) => {
111 assert!(e.to_string().contains("something went wrong"));
112 }
113 _ => panic!("Expected OtherError"),
114 }
115 }
116
117 #[test]
118 fn test_error_result_type() {
119 fn returns_error() -> Result<i32> {
120 Err(AppError::ComponentNotExist("TestComponent"))
121 }
122
123 let result = returns_error();
124 assert!(result.is_err());
125
126 match result {
127 Err(AppError::ComponentNotExist(name)) => {
128 assert_eq!(name, "TestComponent");
129 }
130 _ => panic!("Expected ComponentNotExist error"),
131 }
132 }
133
134 #[test]
135 fn test_error_result_ok() {
136 fn returns_ok() -> Result<String> {
137 Ok("success".to_string())
138 }
139
140 let result = returns_ok();
141 assert!(result.is_ok());
142 assert_eq!(result.unwrap(), "success");
143 }
144
145 #[test]
146 fn test_error_chain() {
147 fn nested_error() -> Result<()> {
148 std::fs::read_to_string("/nonexistent/file.txt")?;
149 Ok(())
150 }
151
152 let result = nested_error();
153 assert!(result.is_err());
154
155 match result {
156 Err(AppError::IOError(_)) => {
157 }
159 _ => panic!("Expected IOError from file operation"),
160 }
161 }
162
163 #[test]
164 fn test_all_error_variants_display() {
165 let errors = vec![
166 AppError::ComponentNotExist("Test"),
167 AppError::from_io(ErrorKind::NotFound, "test"),
168 AppError::TomlMergeError("test merge".to_string()),
169 ];
170
171 for error in errors {
172 let msg = error.to_string();
174 assert!(!msg.is_empty(), "Error message should not be empty");
175 }
176 }
177}