backup_suite/smart/
error.rs1use thiserror::Error;
6
7#[derive(Error, Debug)]
9pub enum SmartError {
10 #[error("統計計算エラー: {0}")]
12 StatisticsError(String),
13
14 #[error("予測モデルエラー: {0}")]
16 PredictionError(String),
17
18 #[error("データ不足: 最低{required}件必要ですが、{actual}件しかありません")]
20 InsufficientData {
21 required: usize,
23 actual: usize,
25 },
26
27 #[error("無効なパラメータ: {0}")]
29 InvalidParameter(String),
30
31 #[error("数値が範囲外です: {value} (範囲: {min} - {max})")]
33 OutOfRange {
34 value: f64,
36 min: f64,
38 max: f64,
40 },
41
42 #[error("数値演算エラー: {0}")]
44 ArithmeticError(String),
45
46 #[error("I/Oエラー: {0}")]
48 IoError(#[from] std::io::Error),
49
50 #[error("セキュリティエラー: {0}")]
52 SecurityError(String),
53
54 #[error("LLM通信エラー: {0}")]
56 LlmCommunicationError(String),
57
58 #[error("Ollama未インストール: Ollamaがインストールされていません。https://ollama.ai からインストールしてください")]
60 OllamaNotInstalled,
61
62 #[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 #[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 #[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 #[must_use]
194 pub const fn is_recoverable(&self) -> bool {
195 matches!(
196 self,
197 SmartError::IoError(_) | SmartError::LlmCommunicationError(_)
198 )
199 }
200
201 #[must_use]
203 pub const fn is_transient(&self) -> bool {
204 matches!(
205 self,
206 SmartError::IoError(_) | SmartError::LlmCommunicationError(_)
207 )
208 }
209}
210
211pub 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}