dampen_cli/commands/add/
errors.rs1use std::path::PathBuf;
4use thiserror::Error;
5
6#[derive(Debug, Error)]
8pub enum ValidationError {
9 #[error("Window name cannot be empty")]
11 EmptyName,
12
13 #[error("Window name must start with a letter or underscore, found '{0}'")]
15 InvalidFirstChar(char),
16
17 #[error(
19 "Window name contains invalid characters (only letters, numbers, and underscores allowed)"
20 )]
21 InvalidCharacters,
22
23 #[error("'{0}' is a reserved name")]
25 ReservedName(String),
26}
27
28#[derive(Debug, Error)]
30pub enum PathError {
31 #[error(
33 "Absolute paths are not allowed: {0}\nhelp: Use a relative path within the project, e.g., 'src/ui/orders/'"
34 )]
35 AbsolutePath(PathBuf),
36
37 #[error(
39 "Path '{path}' is outside the project directory\nhelp: Use a relative path within the project, e.g., 'src/ui/orders/'"
40 )]
41 OutsideProject {
42 path: PathBuf,
44 project_root: PathBuf,
46 },
47
48 #[error("I/O error: {0}")]
50 Io(#[from] std::io::Error),
51}
52
53#[derive(Debug, Error)]
55pub enum ProjectError {
56 #[error(
58 "Not a Dampen project: Cargo.toml not found in current directory or any parent directory\nhelp: Run 'dampen new <project_name>' to create a new Dampen project"
59 )]
60 CargoTomlNotFound,
61
62 #[error(
64 "Not a Dampen project: dampen-core dependency not found in Cargo.toml\nhelp: Add dampen-core to [dependencies] or run 'dampen new' to create a new project"
65 )]
66 NotDampenProject,
67
68 #[error("Failed to read Cargo.toml: {0}")]
70 IoError(#[from] std::io::Error),
71
72 #[error("Failed to parse Cargo.toml: {0}")]
74 ParseError(#[from] toml::de::Error),
75}
76
77#[derive(Debug, Error)]
79pub enum GenerationError {
80 #[error(
82 "Window '{window_name}' already exists at {path}\nhelp: Choose a different name or remove the existing file first"
83 )]
84 FileExists {
85 window_name: String,
87 path: PathBuf,
89 },
90
91 #[error("Failed to create directory {path}: {source}")]
93 DirectoryCreation {
94 path: PathBuf,
96 #[source]
98 source: std::io::Error,
99 },
100
101 #[error("Failed to write file {path}: {source}")]
103 FileWrite {
104 path: PathBuf,
106 #[source]
108 source: std::io::Error,
109 },
110}
111
112#[derive(Debug, Error)]
114pub enum IntegrationError {
115 #[error("Failed to read {path}: {source}")]
117 ModFileRead {
118 path: PathBuf,
120 #[source]
122 source: std::io::Error,
123 },
124
125 #[error("Failed to write {path}: {source}")]
127 ModFileWrite {
128 path: PathBuf,
130 #[source]
132 source: std::io::Error,
133 },
134
135 #[error("Failed to create directory {path}: {source}")]
137 DirectoryCreation {
138 path: PathBuf,
140 #[source]
142 source: std::io::Error,
143 },
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_validation_error_empty_name() {
152 let err = ValidationError::EmptyName;
153 assert_eq!(err.to_string(), "Window name cannot be empty");
154 }
155
156 #[test]
157 fn test_validation_error_invalid_first_char() {
158 let err = ValidationError::InvalidFirstChar('9');
159 assert_eq!(
160 err.to_string(),
161 "Window name must start with a letter or underscore, found '9'"
162 );
163 }
164
165 #[test]
166 fn test_validation_error_invalid_characters() {
167 let err = ValidationError::InvalidCharacters;
168 assert_eq!(
169 err.to_string(),
170 "Window name contains invalid characters (only letters, numbers, and underscores allowed)"
171 );
172 }
173
174 #[test]
175 fn test_validation_error_reserved_name() {
176 let err = ValidationError::ReservedName("mod".to_string());
177 assert_eq!(err.to_string(), "'mod' is a reserved name");
178 }
179
180 #[test]
181 fn test_path_error_absolute_path() {
182 let err = PathError::AbsolutePath(PathBuf::from("/absolute/path"));
183 let msg = err.to_string();
184 assert!(msg.contains("Absolute paths are not allowed"));
185 assert!(msg.contains("/absolute/path"));
186 assert!(msg.contains("help:"));
187 }
188
189 #[test]
190 fn test_path_error_outside_project() {
191 let err = PathError::OutsideProject {
192 path: PathBuf::from("../outside"),
193 project_root: PathBuf::from("/project"),
194 };
195 let msg = err.to_string();
196 assert!(msg.contains("outside the project directory"));
197 assert!(msg.contains("help:"));
198 }
199
200 #[test]
201 fn test_project_error_cargo_toml_not_found() {
202 let err = ProjectError::CargoTomlNotFound;
203 let msg = err.to_string();
204 assert!(msg.contains("Cargo.toml not found"));
205 assert!(msg.contains("help:"));
206 assert!(msg.contains("dampen new"));
207 }
208
209 #[test]
210 fn test_project_error_not_dampen_project() {
211 let err = ProjectError::NotDampenProject;
212 let msg = err.to_string();
213 assert!(msg.contains("dampen-core dependency not found"));
214 assert!(msg.contains("help:"));
215 }
216
217 #[test]
218 fn test_generation_error_file_exists() {
219 let err = GenerationError::FileExists {
220 window_name: "settings".to_string(),
221 path: PathBuf::from("src/ui/settings.rs"),
222 };
223 let msg = err.to_string();
224 assert!(msg.contains("already exists"));
225 assert!(msg.contains("settings"));
226 assert!(msg.contains("help:"));
227 }
228
229 #[test]
230 fn test_generation_error_directory_creation() {
231 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "permission denied");
232 let err = GenerationError::DirectoryCreation {
233 path: PathBuf::from("src/ui/admin"),
234 source: io_err,
235 };
236 let msg = err.to_string();
237 assert!(msg.contains("Failed to create directory"));
238 assert!(msg.contains("src/ui/admin"));
239 }
240
241 #[test]
242 fn test_generation_error_file_write() {
243 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "permission denied");
244 let err = GenerationError::FileWrite {
245 path: PathBuf::from("src/ui/settings.rs"),
246 source: io_err,
247 };
248 let msg = err.to_string();
249 assert!(msg.contains("Failed to write file"));
250 assert!(msg.contains("src/ui/settings.rs"));
251 }
252}