subx_cli/
error.rs

1use thiserror::Error;
2
3/// SubX 應用程式的主要錯誤類型
4#[derive(Error, Debug)]
5pub enum SubXError {
6    /// IO 相關錯誤
7    #[error("IO 錯誤: {0}")]
8    Io(#[from] std::io::Error),
9
10    /// 配置錯誤
11    #[error("配置錯誤: {message}")]
12    Config { message: String },
13
14    /// 字幕格式錯誤
15    #[error("字幕格式錯誤: {format} - {message}")]
16    SubtitleFormat { format: String, message: String },
17
18    /// AI 服務錯誤
19    #[error("AI 服務錯誤: {0}")]
20    AiService(String),
21
22    /// 音訊處理錯誤
23    #[error("音訊處理錯誤: {message}")]
24    AudioProcessing { message: String },
25
26    /// 文件匹配錯誤
27    #[error("文件匹配錯誤: {message}")]
28    FileMatching { message: String },
29    /// 檔案已存在錯誤
30    #[error("檔案已存在: {0}")]
31    FileAlreadyExists(String),
32    /// 檔案不存在錯誤
33    #[error("檔案不存在: {0}")]
34    FileNotFound(String),
35    /// 無效的檔案名稱錯誤
36    #[error("無效的檔案名稱: {0}")]
37    InvalidFileName(String),
38    /// 檔案操作失敗錯誤
39    #[error("檔案操作失敗: {0}")]
40    FileOperationFailed(String),
41    /// 命令執行錯誤
42    #[error("{0}")]
43    CommandExecution(String),
44
45    /// 一般錯誤
46    #[error("未知錯誤: {0}")]
47    Other(#[from] anyhow::Error),
48}
49
50// 單元測試: SubXError 錯誤類型與輔助方法
51#[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
115// 將 reqwest 錯誤轉換為 AI 服務錯誤
116impl From<reqwest::Error> for SubXError {
117    fn from(err: reqwest::Error) -> Self {
118        SubXError::AiService(err.to_string())
119    }
120}
121
122// 將檔案探索錯誤轉換為文件匹配錯誤
123impl From<walkdir::Error> for SubXError {
124    fn from(err: walkdir::Error) -> Self {
125        SubXError::FileMatching {
126            message: err.to_string(),
127        }
128    }
129}
130// 將 symphonia 錯誤轉換為音訊處理錯誤
131impl 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}
136/// SubX 應用程式的 Result 類型
137pub type SubXResult<T> = Result<T, SubXError>;
138
139impl SubXError {
140    /// 建立配置錯誤
141    pub fn config<S: Into<String>>(message: S) -> Self {
142        SubXError::Config {
143            message: message.into(),
144        }
145    }
146
147    /// 建立字幕格式錯誤
148    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    /// 建立音訊處理錯誤
160    pub fn audio_processing<S: Into<String>>(message: S) -> Self {
161        SubXError::AudioProcessing {
162            message: message.into(),
163        }
164    }
165
166    /// 建立 AI 服務錯誤
167    pub fn ai_service<S: Into<String>>(message: S) -> Self {
168        SubXError::AiService(message.into())
169    }
170
171    /// 建立文件匹配錯誤
172    pub fn file_matching<S: Into<String>>(message: S) -> Self {
173        SubXError::FileMatching {
174            message: message.into(),
175        }
176    }
177    /// 建立平行處理錯誤
178    pub fn parallel_processing(msg: String) -> Self {
179        SubXError::CommandExecution(format!("並行處理錯誤: {}", msg))
180    }
181    /// 建立任務執行失敗錯誤
182    pub fn task_execution_failed(task_id: String, reason: String) -> Self {
183        SubXError::CommandExecution(format!("任務 {} 執行失敗: {}", task_id, reason))
184    }
185    /// 建立工作者池耗盡錯誤
186    pub fn worker_pool_exhausted() -> Self {
187        SubXError::CommandExecution("工作者池資源已耗盡".to_string())
188    }
189    /// 建立任務超時錯誤
190    pub fn task_timeout(task_id: String, duration: std::time::Duration) -> Self {
191        SubXError::CommandExecution(format!(
192            "任務 {} 執行超時,限制時間: {:?}",
193            task_id, duration
194        ))
195    }
196    /// 建立對話檢測失敗錯誤
197    pub fn dialogue_detection_failed<S: Into<String>>(msg: S) -> Self {
198        SubXError::AudioProcessing {
199            message: format!("對話檢測失敗: {}", msg.into()),
200        }
201    }
202    /// 建立無效音訊格式錯誤
203    pub fn invalid_audio_format<S: Into<String>>(format: S) -> Self {
204        SubXError::AudioProcessing {
205            message: format!("不支援的音訊格式: {}", format.into()),
206        }
207    }
208    /// 建立無效對話片段錯誤
209    pub fn dialogue_segment_invalid<S: Into<String>>(reason: S) -> Self {
210        SubXError::AudioProcessing {
211            message: format!("無效的對話片段: {}", reason.into()),
212        }
213    }
214    /// 取得對應退出狀態碼
215    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    /// 取得用戶友善的錯誤訊息
228    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
264// aus 錯誤轉換
265impl 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}