1use std::path::PathBuf;
2use thiserror::Error;
3
4#[derive(Error, Debug)]
9pub enum BackupError {
10 #[error("ホームディレクトリが見つかりません")]
12 HomeDirectoryNotFound,
13
14 #[error("バックアップ対象が存在しません: {path}")]
16 TargetNotFound { path: PathBuf },
17
18 #[error("読み取り権限がありません: {path}")]
20 PermissionDenied { path: PathBuf },
21
22 #[error("不正なパス(ディレクトリトラバーサル検出): {path}")]
24 PathTraversalDetected { path: PathBuf },
25
26 #[error("親ディレクトリが見つかりません: {path}")]
28 ParentDirectoryNotFound { path: PathBuf },
29
30 #[error("設定ファイルの読み込みに失敗: {0}")]
32 ConfigLoadError(#[from] toml::de::Error),
33
34 #[error("設定ファイルのパースに失敗: {message}")]
36 ConfigParseError { message: String },
37
38 #[error("設定の検証に失敗: {message}")]
40 ConfigValidationError { message: String },
41
42 #[error("I/Oエラー: {0}")]
44 IoError(#[from] std::io::Error),
45
46 #[error("正規表現のコンパイルに失敗: {pattern}")]
48 RegexError {
49 pattern: String,
50 #[source]
51 source: regex::Error,
52 },
53
54 #[error("ファイルコピーに失敗: {from} → {to}")]
56 FileCopyError { from: PathBuf, to: PathBuf },
57
58 #[error("バックアップディレクトリ作成に失敗: {path}")]
60 BackupDirectoryCreationError { path: PathBuf },
61
62 #[error("暗号化エラー: {0}")]
64 EncryptionError(String),
65
66 #[error("圧縮エラー: {0}")]
68 CompressionError(String),
69
70 #[error("エラー: {0}")]
72 Other(#[from] anyhow::Error),
73}
74
75pub type Result<T> = std::result::Result<T, BackupError>;
88
89impl BackupError {
90 #[must_use]
97 pub fn is_recoverable(&self) -> bool {
98 matches!(
99 self,
100 BackupError::IoError(_) | BackupError::FileCopyError { .. }
101 )
102 }
103
104 #[must_use]
111 pub fn is_security_related(&self) -> bool {
112 matches!(
113 self,
114 BackupError::PathTraversalDetected { .. } | BackupError::PermissionDenied { .. }
115 )
116 }
117
118 #[must_use]
124 pub fn user_friendly_message(&self) -> String {
125 match self {
126 BackupError::HomeDirectoryNotFound => "ホームディレクトリが見つかりません。\n\
127 対処法: 環境変数 $HOME が設定されているか確認してください。"
128 .to_string(),
129 BackupError::TargetNotFound { path } => {
130 format!(
131 "バックアップ対象が存在しません: {}\n\
132 対処法: パスが正しいか、ファイル/ディレクトリが存在するか確認してください。",
133 path.display()
134 )
135 }
136 BackupError::PermissionDenied { path } => {
137 format!(
138 "読み取り権限がありません: {}\n\
139 対処法: ファイル/ディレクトリの権限を確認するか、sudo で実行してください。",
140 path.display()
141 )
142 }
143 BackupError::PathTraversalDetected { path } => {
144 format!(
145 "不正なパスが検出されました: {}\n\
146 セキュリティ警告: ディレクトリトラバーサル攻撃の可能性があります。",
147 path.display()
148 )
149 }
150 BackupError::ConfigValidationError { message } => {
151 format!(
152 "設定に問題があります: {message}\n\
153 対処法: ~/.config/backup-suite/config.toml を確認してください。"
154 )
155 }
156 BackupError::FileCopyError { from, to } => {
157 format!(
158 "ファイルコピーに失敗しました:\n\
159 元: {}\n\
160 先: {}\n\
161 対処法: ディスク容量と権限を確認してください。",
162 from.display(),
163 to.display()
164 )
165 }
166 _ => self.to_string(),
167 }
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_error_is_recoverable() {
177 let io_error =
178 BackupError::IoError(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
179 assert!(io_error.is_recoverable());
180
181 let permission_error = BackupError::PermissionDenied {
182 path: PathBuf::from("/test"),
183 };
184 assert!(!permission_error.is_recoverable());
185 }
186
187 #[test]
188 fn test_error_is_security_related() {
189 let path_traversal = BackupError::PathTraversalDetected {
190 path: PathBuf::from("../../../etc/passwd"),
191 };
192 assert!(path_traversal.is_security_related());
193
194 let io_error =
195 BackupError::IoError(std::io::Error::new(std::io::ErrorKind::NotFound, "test"));
196 assert!(!io_error.is_security_related());
197 }
198
199 #[test]
200 fn test_user_friendly_message() {
201 let error = BackupError::TargetNotFound {
202 path: PathBuf::from("/nonexistent"),
203 };
204 let message = error.user_friendly_message();
205 assert!(message.contains("対処法"));
206 assert!(message.contains("/nonexistent"));
207 }
208}