backup_suite/smart/
error.rs

1//! Smart機能のエラー型定義
2//!
3//! thiserrorを使用した型安全なエラーハンドリングを提供します。
4
5use thiserror::Error;
6
7/// Smart機能のエラー型
8#[derive(Error, Debug)]
9pub enum SmartError {
10    /// 統計計算エラー
11    #[error("統計計算エラー: {0}")]
12    StatisticsError(String),
13
14    /// 予測モデルエラー
15    #[error("予測モデルエラー: {0}")]
16    PredictionError(String),
17
18    /// データ不足エラー
19    #[error("データ不足: 最低{required}件必要ですが、{actual}件しかありません")]
20    InsufficientData {
21        /// 必要なデータ数
22        required: usize,
23        /// 実際のデータ数
24        actual: usize,
25    },
26
27    /// 無効なパラメータ
28    #[error("無効なパラメータ: {0}")]
29    InvalidParameter(String),
30
31    /// 数値範囲外エラー
32    #[error("数値が範囲外です: {value} (範囲: {min} - {max})")]
33    OutOfRange {
34        /// 入力値
35        value: f64,
36        /// 最小値
37        min: f64,
38        /// 最大値
39        max: f64,
40    },
41
42    /// オーバーフロー/アンダーフローエラー
43    #[error("数値演算エラー: {0}")]
44    ArithmeticError(String),
45
46    /// I/Oエラー
47    #[error("I/Oエラー: {0}")]
48    IoError(#[from] std::io::Error),
49
50    /// セキュリティエラー(BackupErrorから変換)
51    #[error("セキュリティエラー: {0}")]
52    SecurityError(String),
53
54    /// LLM通信エラー
55    #[error("LLM通信エラー: {0}")]
56    LlmCommunicationError(String),
57
58    /// Ollama未インストールエラー
59    #[error("Ollama未インストール: Ollamaがインストールされていません。https://ollama.ai からインストールしてください")]
60    OllamaNotInstalled,
61
62    /// その他のエラー
63    #[error("Smartエラー: {0}")]
64    Other(#[from] anyhow::Error),
65}
66
67impl From<crate::error::BackupError> for SmartError {
68    fn from(err: crate::error::BackupError) -> Self {
69        match err {
70            crate::error::BackupError::PathTraversalDetected { path } => {
71                SmartError::SecurityError(format!("パストラバーサル検出: {:?}", path))
72            }
73            crate::error::BackupError::PermissionDenied { path } => {
74                SmartError::SecurityError(format!("権限エラー: {:?}", path))
75            }
76            crate::error::BackupError::IoError(e) => SmartError::IoError(e),
77            other => SmartError::Other(anyhow::Error::new(other)),
78        }
79    }
80}
81
82impl SmartError {
83    /// ユーザーフレンドリーなエラーメッセージを生成
84    ///
85    /// # 使用例
86    ///
87    /// ```rust
88    /// use backup_suite::smart::error::SmartError;
89    ///
90    /// let error = SmartError::InsufficientData {
91    ///     required: 10,
92    ///     actual: 3,
93    /// };
94    /// let message = error.user_friendly_message();
95    /// assert!(message.contains("データが不足"));
96    /// ```
97    #[must_use]
98    pub fn user_friendly_message(&self) -> String {
99        match self {
100            SmartError::StatisticsError(_) | SmartError::PredictionError(_) => {
101                format!("分析処理中にエラーが発生しました: {}", self)
102            }
103            SmartError::InsufficientData { required, actual } => {
104                format!(
105                    "分析に必要なデータが不足しています。最低{}件必要ですが、{}件しかありません。",
106                    required, actual
107                )
108            }
109            SmartError::InvalidParameter(msg) => {
110                format!("設定値が不正です: {}", msg)
111            }
112            SmartError::OutOfRange { value, min, max } => {
113                format!("値{}が許容範囲({} - {})外です", value, min, max)
114            }
115            SmartError::ArithmeticError(msg) => {
116                format!("計算処理中にエラーが発生しました: {}", msg)
117            }
118            SmartError::IoError(e) => {
119                format!("ファイル操作中にエラーが発生しました: {}", e)
120            }
121            SmartError::SecurityError(msg) => {
122                format!("セキュリティエラー: {}", msg)
123            }
124            SmartError::LlmCommunicationError(msg) => {
125                format!(
126                    "AI推論エンジンとの通信に失敗しました: {}。Ollamaサービスが起動しているか確認してください。",
127                    msg
128                )
129            }
130            SmartError::OllamaNotInstalled => {
131                "Ollamaがインストールされていません。AI機能を使用するには https://ollama.ai からOllamaをインストールしてください。".to_string()
132            }
133            SmartError::Other(e) => {
134                format!("エラーが発生しました: {}", e)
135            }
136        }
137    }
138
139    /// ユーザーフレンドリーなエラーメッセージを生成(英語)
140    ///
141    /// # 使用例
142    ///
143    /// ```rust
144    /// use backup_suite::smart::error::SmartError;
145    ///
146    /// let error = SmartError::OllamaNotInstalled;
147    /// let message = error.user_friendly_message_en();
148    /// assert!(message.contains("Ollama"));
149    /// ```
150    #[must_use]
151    pub fn user_friendly_message_en(&self) -> String {
152        match self {
153            SmartError::StatisticsError(_) | SmartError::PredictionError(_) => {
154                format!("An error occurred during analysis: {}", self)
155            }
156            SmartError::InsufficientData { required, actual } => {
157                format!(
158                    "Insufficient data for analysis. At least {} items required, but only {} available.",
159                    required, actual
160                )
161            }
162            SmartError::InvalidParameter(msg) => {
163                format!("Invalid parameter: {}", msg)
164            }
165            SmartError::OutOfRange { value, min, max } => {
166                format!("Value {} is out of range ({} - {})", value, min, max)
167            }
168            SmartError::ArithmeticError(msg) => {
169                format!("Arithmetic error occurred: {}", msg)
170            }
171            SmartError::IoError(e) => {
172                format!("I/O error occurred: {}", e)
173            }
174            SmartError::SecurityError(msg) => {
175                format!("Security error: {}", msg)
176            }
177            SmartError::LlmCommunicationError(msg) => {
178                format!(
179                    "Failed to communicate with AI inference engine: {}. Please check if Ollama service is running.",
180                    msg
181                )
182            }
183            SmartError::OllamaNotInstalled => {
184                "Ollama is not installed. Please install Ollama from https://ollama.ai to use AI features.".to_string()
185            }
186            SmartError::Other(e) => {
187                format!("An error occurred: {}", e)
188            }
189        }
190    }
191
192    /// リトライ可能なエラーかどうかを判定
193    #[must_use]
194    pub const fn is_recoverable(&self) -> bool {
195        matches!(
196            self,
197            SmartError::IoError(_) | SmartError::LlmCommunicationError(_)
198        )
199    }
200
201    /// 一時的なエラーかどうかを判定
202    #[must_use]
203    pub const fn is_transient(&self) -> bool {
204        matches!(
205            self,
206            SmartError::IoError(_) | SmartError::LlmCommunicationError(_)
207        )
208    }
209}
210
211/// Smart機能のResult型エイリアス
212pub type SmartResult<T> = std::result::Result<T, SmartError>;
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn test_insufficient_data_error() {
220        let error = SmartError::InsufficientData {
221            required: 10,
222            actual: 3,
223        };
224        let msg = error.to_string();
225        assert!(msg.contains("最低10件必要"));
226        assert!(msg.contains("3件しか"));
227    }
228
229    #[test]
230    fn test_out_of_range_error() {
231        let error = SmartError::OutOfRange {
232            value: 150.0,
233            min: 0.0,
234            max: 100.0,
235        };
236        let msg = error.to_string();
237        assert!(msg.contains("150"));
238        assert!(msg.contains("0"));
239        assert!(msg.contains("100"));
240    }
241
242    #[test]
243    fn test_user_friendly_message() {
244        let error = SmartError::InvalidParameter("閾値が負の数です".to_string());
245        let msg = error.user_friendly_message();
246        assert!(msg.contains("設定値が不正"));
247    }
248
249    #[test]
250    fn test_error_recovery() {
251        let io_error =
252            SmartError::IoError(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
253        assert!(io_error.is_recoverable());
254        assert!(io_error.is_transient());
255
256        let stat_error = SmartError::StatisticsError("test".to_string());
257        assert!(!stat_error.is_recoverable());
258        assert!(!stat_error.is_transient());
259    }
260
261    #[test]
262    fn test_from_io_error() {
263        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "test");
264        let smart_error: SmartError = io_error.into();
265        assert!(matches!(smart_error, SmartError::IoError(_)));
266    }
267
268    #[test]
269    fn test_llm_communication_error() {
270        let error = SmartError::LlmCommunicationError("connection timeout".to_string());
271        let msg = error.user_friendly_message();
272        assert!(msg.contains("AI推論エンジン"));
273        assert!(msg.contains("Ollama"));
274
275        let msg_en = error.user_friendly_message_en();
276        assert!(msg_en.contains("AI inference engine"));
277        assert!(msg_en.contains("Ollama"));
278
279        assert!(error.is_recoverable());
280        assert!(error.is_transient());
281    }
282
283    #[test]
284    fn test_ollama_not_installed() {
285        let error = SmartError::OllamaNotInstalled;
286        let msg = error.user_friendly_message();
287        assert!(msg.contains("Ollama"));
288        assert!(msg.contains("https://ollama.ai"));
289
290        let msg_en = error.user_friendly_message_en();
291        assert!(msg_en.contains("Ollama"));
292        assert!(msg_en.contains("https://ollama.ai"));
293
294        assert!(!error.is_recoverable());
295        assert!(!error.is_transient());
296    }
297
298    #[test]
299    fn test_user_friendly_message_en() {
300        let error = SmartError::InsufficientData {
301            required: 10,
302            actual: 3,
303        };
304        let msg = error.user_friendly_message_en();
305        assert!(msg.contains("Insufficient data"));
306        assert!(msg.contains("10"));
307        assert!(msg.contains("3"));
308    }
309}