1use thiserror::Error;
2
3#[derive(Error, Debug)]
5pub enum SubXError {
6 #[error("IO 錯誤: {0}")]
8 Io(#[from] std::io::Error),
9
10 #[error("配置錯誤: {message}")]
12 Config { message: String },
13
14 #[error("字幕格式錯誤: {format} - {message}")]
16 SubtitleFormat { format: String, message: String },
17
18 #[error("AI 服務錯誤: {0}")]
20 AiService(String),
21
22 #[error("音訊處理錯誤: {message}")]
24 AudioProcessing { message: String },
25
26 #[error("文件匹配錯誤: {message}")]
28 FileMatching { message: String },
29 #[error("檔案已存在: {0}")]
31 FileAlreadyExists(String),
32 #[error("檔案不存在: {0}")]
34 FileNotFound(String),
35 #[error("無效的檔案名稱: {0}")]
37 InvalidFileName(String),
38 #[error("檔案操作失敗: {0}")]
40 FileOperationFailed(String),
41 #[error("{0}")]
43 CommandExecution(String),
44
45 #[error("未知錯誤: {0}")]
47 Other(#[from] anyhow::Error),
48}
49
50#[cfg(test)]
52mod tests {
53 use super::*;
54 use std::io;
55
56 #[test]
57 fn test_config_error_creation() {
58 let error = SubXError::config("測試配置錯誤");
59 assert!(matches!(error, SubXError::Config { .. }));
60 assert_eq!(error.to_string(), "配置錯誤: 測試配置錯誤");
61 }
62
63 #[test]
64 fn test_subtitle_format_error_creation() {
65 let error = SubXError::subtitle_format("SRT", "無效格式");
66 assert!(matches!(error, SubXError::SubtitleFormat { .. }));
67 let msg = error.to_string();
68 assert!(msg.contains("SRT"));
69 assert!(msg.contains("無效格式"));
70 }
71
72 #[test]
73 fn test_audio_processing_error_creation() {
74 let error = SubXError::audio_processing("音訊解碼失敗");
75 assert!(matches!(error, SubXError::AudioProcessing { .. }));
76 assert_eq!(error.to_string(), "音訊處理錯誤: 音訊解碼失敗");
77 }
78
79 #[test]
80 fn test_file_matching_error_creation() {
81 let error = SubXError::file_matching("匹配失敗");
82 assert!(matches!(error, SubXError::FileMatching { .. }));
83 assert_eq!(error.to_string(), "文件匹配錯誤: 匹配失敗");
84 }
85
86 #[test]
87 fn test_io_error_conversion() {
88 let io_error = io::Error::new(io::ErrorKind::NotFound, "檔案不存在");
89 let subx_error: SubXError = io_error.into();
90 assert!(matches!(subx_error, SubXError::Io(_)));
91 }
92
93 #[test]
94 fn test_exit_codes() {
95 assert_eq!(SubXError::config("test").exit_code(), 2);
96 assert_eq!(SubXError::subtitle_format("SRT", "test").exit_code(), 4);
97 assert_eq!(SubXError::audio_processing("test").exit_code(), 5);
98 assert_eq!(SubXError::file_matching("test").exit_code(), 6);
99 }
100
101 #[test]
102 fn test_user_friendly_messages() {
103 let config_error = SubXError::config("API 金鑰未設定");
104 let message = config_error.user_friendly_message();
105 assert!(message.contains("配置錯誤"));
106 assert!(message.contains("subx config --help"));
107
108 let ai_error = SubXError::ai_service("網路連接失敗".to_string());
109 let message = ai_error.user_friendly_message();
110 assert!(message.contains("AI 服務錯誤"));
111 assert!(message.contains("檢查網路連接"));
112 }
113}
114
115impl From<reqwest::Error> for SubXError {
117 fn from(err: reqwest::Error) -> Self {
118 SubXError::AiService(err.to_string())
119 }
120}
121
122impl From<walkdir::Error> for SubXError {
124 fn from(err: walkdir::Error) -> Self {
125 SubXError::FileMatching {
126 message: err.to_string(),
127 }
128 }
129}
130impl From<symphonia::core::errors::Error> for SubXError {
132 fn from(err: symphonia::core::errors::Error) -> Self {
133 SubXError::audio_processing(err.to_string())
134 }
135}
136pub type SubXResult<T> = Result<T, SubXError>;
138
139impl SubXError {
140 pub fn config<S: Into<String>>(message: S) -> Self {
142 SubXError::Config {
143 message: message.into(),
144 }
145 }
146
147 pub fn subtitle_format<S1, S2>(format: S1, message: S2) -> Self
149 where
150 S1: Into<String>,
151 S2: Into<String>,
152 {
153 SubXError::SubtitleFormat {
154 format: format.into(),
155 message: message.into(),
156 }
157 }
158
159 pub fn audio_processing<S: Into<String>>(message: S) -> Self {
161 SubXError::AudioProcessing {
162 message: message.into(),
163 }
164 }
165
166 pub fn ai_service<S: Into<String>>(message: S) -> Self {
168 SubXError::AiService(message.into())
169 }
170
171 pub fn file_matching<S: Into<String>>(message: S) -> Self {
173 SubXError::FileMatching {
174 message: message.into(),
175 }
176 }
177 pub fn parallel_processing(msg: String) -> Self {
179 SubXError::CommandExecution(format!("並行處理錯誤: {}", msg))
180 }
181 pub fn task_execution_failed(task_id: String, reason: String) -> Self {
183 SubXError::CommandExecution(format!("任務 {} 執行失敗: {}", task_id, reason))
184 }
185 pub fn worker_pool_exhausted() -> Self {
187 SubXError::CommandExecution("工作者池資源已耗盡".to_string())
188 }
189 pub fn task_timeout(task_id: String, duration: std::time::Duration) -> Self {
191 SubXError::CommandExecution(format!(
192 "任務 {} 執行超時,限制時間: {:?}",
193 task_id, duration
194 ))
195 }
196 pub fn dialogue_detection_failed<S: Into<String>>(msg: S) -> Self {
198 SubXError::AudioProcessing {
199 message: format!("對話檢測失敗: {}", msg.into()),
200 }
201 }
202 pub fn invalid_audio_format<S: Into<String>>(format: S) -> Self {
204 SubXError::AudioProcessing {
205 message: format!("不支援的音訊格式: {}", format.into()),
206 }
207 }
208 pub fn dialogue_segment_invalid<S: Into<String>>(reason: S) -> Self {
210 SubXError::AudioProcessing {
211 message: format!("無效的對話片段: {}", reason.into()),
212 }
213 }
214 pub fn exit_code(&self) -> i32 {
216 match self {
217 SubXError::Io(_) => 1,
218 SubXError::Config { .. } => 2,
219 SubXError::AiService(_) => 3,
220 SubXError::SubtitleFormat { .. } => 4,
221 SubXError::AudioProcessing { .. } => 5,
222 SubXError::FileMatching { .. } => 6,
223 _ => 1,
224 }
225 }
226
227 pub fn user_friendly_message(&self) -> String {
229 match self {
230 SubXError::Io(e) => format!("檔案操作錯誤: {}", e),
231 SubXError::Config { message } => {
232 format!(
233 "配置錯誤: {}\n提示: 使用 'subx config --help' 查看配置說明",
234 message
235 )
236 }
237 SubXError::AiService(msg) => {
238 format!("AI 服務錯誤: {}\n提示: 檢查網路連接和 API 金鑰設定", msg)
239 }
240 SubXError::SubtitleFormat { message, .. } => {
241 format!("字幕處理錯誤: {}\n提示: 檢查檔案格式和編碼", message)
242 }
243 SubXError::AudioProcessing { message } => {
244 format!(
245 "音訊處理錯誤: {}\n提示: 確認影片檔案完整且格式支援",
246 message
247 )
248 }
249 SubXError::FileMatching { message } => {
250 format!("檔案匹配錯誤: {}\n提示: 檢查檔案路徑和格式", message)
251 }
252 SubXError::FileAlreadyExists(path) => format!("檔案已存在: {}", path),
253 SubXError::FileNotFound(path) => format!("檔案不存在: {}", path),
254 SubXError::InvalidFileName(name) => format!("無效的檔案名稱: {}", name),
255 SubXError::FileOperationFailed(msg) => format!("檔案操作失敗: {}", msg),
256 SubXError::CommandExecution(msg) => msg.clone(),
257 SubXError::Other(err) => {
258 format!("未知錯誤: {}\n提示: 請回報此問題", err)
259 }
260 }
261 }
262}
263
264impl From<aus::AudioError> for SubXError {
266 fn from(error: aus::AudioError) -> Self {
267 SubXError::audio_processing(format!("{:?}", error))
268 }
269}
270
271impl From<aus::spectrum::SpectrumError> for SubXError {
272 fn from(error: aus::spectrum::SpectrumError) -> Self {
273 SubXError::audio_processing(format!("頻譜處理錯誤: {:?}", error))
274 }
275}