torrust_tracker_deployer_lib/domain/template/
file_ops.rs1use std::path::Path;
7use thiserror::Error;
8
9#[derive(Error, Debug)]
11pub enum FileOperationError {
12 #[error("Failed to create directory: {path}")]
14 DirectoryCreation { path: String },
15
16 #[error("Failed to write file to: {path}")]
18 FileWrite { path: String },
19
20 #[error("Failed to copy file from {source_path} to {dest_path}")]
22 FileCopy {
23 source_path: String,
24 dest_path: String,
25 },
26}
27
28pub fn copy_file_with_dir_creation(
37 source: &Path,
38 destination: &Path,
39) -> Result<(), FileOperationError> {
40 if let Some(parent) = destination.parent() {
42 std::fs::create_dir_all(parent).map_err(|_| FileOperationError::DirectoryCreation {
43 path: parent.display().to_string(),
44 })?;
45 }
46
47 std::fs::copy(source, destination).map_err(|_| FileOperationError::FileCopy {
48 source_path: source.display().to_string(),
49 dest_path: destination.display().to_string(),
50 })?;
51
52 Ok(())
53}
54
55pub fn write_file_with_dir_creation(
61 output_path: &Path,
62 content: &str,
63) -> Result<(), FileOperationError> {
64 if let Some(parent) = output_path.parent() {
66 std::fs::create_dir_all(parent).map_err(|_| FileOperationError::DirectoryCreation {
67 path: parent.display().to_string(),
68 })?;
69 }
70
71 std::fs::write(output_path, content).map_err(|_| FileOperationError::FileWrite {
73 path: output_path.display().to_string(),
74 })?;
75
76 Ok(())
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use std::fs;
83 use tempfile::TempDir;
84
85 mod copy_file_with_dir_creation {
86 use super::*;
87
88 #[test]
89 fn it_should_copy_file_to_existing_directory() {
90 let temp_dir = TempDir::new().unwrap();
91 let source_file = temp_dir.path().join("source.txt");
92 let dest_file = temp_dir.path().join("dest.txt");
93
94 fs::write(&source_file, "test content").unwrap();
95
96 let result = copy_file_with_dir_creation(&source_file, &dest_file);
97
98 assert!(result.is_ok());
99 assert!(dest_file.exists());
100 let content = fs::read_to_string(&dest_file).unwrap();
101 assert_eq!(content, "test content");
102 }
103
104 #[test]
105 fn it_should_copy_file_and_create_parent_directories() {
106 let temp_dir = TempDir::new().unwrap();
107 let source_file = temp_dir.path().join("source.txt");
108 let dest_file = temp_dir.path().join("deep/nested/path/dest.txt");
109
110 fs::write(&source_file, "nested test").unwrap();
111
112 let result = copy_file_with_dir_creation(&source_file, &dest_file);
113
114 assert!(result.is_ok());
115 assert!(dest_file.exists());
116 let content = fs::read_to_string(&dest_file).unwrap();
117 assert_eq!(content, "nested test");
118 }
119
120 #[test]
121 fn it_should_fail_when_source_file_does_not_exist() {
122 let temp_dir = TempDir::new().unwrap();
123 let source_file = temp_dir.path().join("nonexistent.txt");
124 let dest_file = temp_dir.path().join("dest.txt");
125
126 let result = copy_file_with_dir_creation(&source_file, &dest_file);
127
128 assert!(result.is_err());
129 match result.unwrap_err() {
130 FileOperationError::FileCopy {
131 source_path,
132 dest_path,
133 } => {
134 assert!(source_path.contains("nonexistent.txt"));
135 assert!(dest_path.contains("dest.txt"));
136 }
137 FileOperationError::DirectoryCreation { .. } => {
138 panic!("Expected FileCopy error, got DirectoryCreation")
139 }
140 FileOperationError::FileWrite { .. } => {
141 panic!("Expected FileCopy error, got FileWrite")
142 }
143 }
144 }
145
146 #[test]
147 fn it_should_overwrite_existing_destination_file() {
148 let temp_dir = TempDir::new().unwrap();
149 let source_file = temp_dir.path().join("source.txt");
150 let dest_file = temp_dir.path().join("dest.txt");
151
152 fs::write(&source_file, "new content").unwrap();
153 fs::write(&dest_file, "old content").unwrap();
154
155 let result = copy_file_with_dir_creation(&source_file, &dest_file);
156
157 assert!(result.is_ok());
158 let content = fs::read_to_string(&dest_file).unwrap();
159 assert_eq!(content, "new content");
160 }
161
162 #[test]
163 fn it_should_copy_binary_file_correctly() {
164 let temp_dir = TempDir::new().unwrap();
165 let source_file = temp_dir.path().join("binary.bin");
166 let dest_file = temp_dir.path().join("copied.bin");
167
168 let binary_data = vec![0x00, 0x01, 0xFF, 0x7F, 0x80];
169 fs::write(&source_file, &binary_data).unwrap();
170
171 let result = copy_file_with_dir_creation(&source_file, &dest_file);
172
173 assert!(result.is_ok());
174 let copied_data = fs::read(&dest_file).unwrap();
175 assert_eq!(copied_data, binary_data);
176 }
177 }
178
179 mod write_file_with_dir_creation {
180 use super::*;
181
182 #[test]
183 fn it_should_write_content_to_existing_directory() {
184 let temp_dir = TempDir::new().unwrap();
185 let file_path = temp_dir.path().join("test.txt");
186 let content = "hello world";
187
188 let result = write_file_with_dir_creation(&file_path, content);
189
190 assert!(result.is_ok());
191 assert!(file_path.exists());
192 let read_content = fs::read_to_string(&file_path).unwrap();
193 assert_eq!(read_content, content);
194 }
195
196 #[test]
197 fn it_should_write_file_and_create_parent_directories() {
198 let temp_dir = TempDir::new().unwrap();
199 let file_path = temp_dir.path().join("deep/nested/structure/file.txt");
200 let content = "nested content";
201
202 let result = write_file_with_dir_creation(&file_path, content);
203
204 assert!(result.is_ok());
205 assert!(file_path.exists());
206 let read_content = fs::read_to_string(&file_path).unwrap();
207 assert_eq!(read_content, content);
208 }
209
210 #[test]
211 fn it_should_overwrite_existing_file() {
212 let temp_dir = TempDir::new().unwrap();
213 let file_path = temp_dir.path().join("existing.txt");
214
215 fs::write(&file_path, "original content").unwrap();
216
217 let new_content = "updated content";
218 let result = write_file_with_dir_creation(&file_path, new_content);
219
220 assert!(result.is_ok());
221 let read_content = fs::read_to_string(&file_path).unwrap();
222 assert_eq!(read_content, new_content);
223 }
224
225 #[test]
226 fn it_should_handle_empty_content() {
227 let temp_dir = TempDir::new().unwrap();
228 let file_path = temp_dir.path().join("empty.txt");
229
230 let result = write_file_with_dir_creation(&file_path, "");
231
232 assert!(result.is_ok());
233 assert!(file_path.exists());
234 let read_content = fs::read_to_string(&file_path).unwrap();
235 assert_eq!(read_content, "");
236 }
237
238 #[test]
239 fn it_should_handle_unicode_content() {
240 let temp_dir = TempDir::new().unwrap();
241 let file_path = temp_dir.path().join("unicode.txt");
242 let content = "Hello 世界! 🚀 Émojis and spëcial chars";
243
244 let result = write_file_with_dir_creation(&file_path, content);
245
246 assert!(result.is_ok());
247 let read_content = fs::read_to_string(&file_path).unwrap();
248 assert_eq!(read_content, content);
249 }
250
251 #[test]
252 fn it_should_handle_multiline_content() {
253 let temp_dir = TempDir::new().unwrap();
254 let file_path = temp_dir.path().join("multiline.txt");
255 let content = "Line 1\nLine 2\nLine 3\n\nLine 5";
256
257 let result = write_file_with_dir_creation(&file_path, content);
258
259 assert!(result.is_ok());
260 let read_content = fs::read_to_string(&file_path).unwrap();
261 assert_eq!(read_content, content);
262 }
263 }
264
265 mod error_handling {
266 use super::*;
267
268 #[test]
269 fn it_should_display_directory_creation_error_correctly() {
270 let error = FileOperationError::DirectoryCreation {
271 path: "/some/path".to_string(),
272 };
273 let error_string = format!("{error}");
274 assert!(error_string.contains("Failed to create directory"));
275 assert!(error_string.contains("/some/path"));
276 }
277
278 #[test]
279 fn it_should_display_file_write_error_correctly() {
280 let error = FileOperationError::FileWrite {
281 path: "/output/file.txt".to_string(),
282 };
283 let error_string = format!("{error}");
284 assert!(error_string.contains("Failed to write file to"));
285 assert!(error_string.contains("/output/file.txt"));
286 }
287
288 #[test]
289 fn it_should_display_file_copy_error_correctly() {
290 let error = FileOperationError::FileCopy {
291 source_path: "/source/file.txt".to_string(),
292 dest_path: "/dest/file.txt".to_string(),
293 };
294 let error_string = format!("{error}");
295 assert!(error_string.contains("Failed to copy file from"));
296 assert!(error_string.contains("/source/file.txt"));
297 assert!(error_string.contains("/dest/file.txt"));
298 }
299
300 #[test]
301 fn it_should_support_debug_formatting_for_errors() {
302 let write_error = FileOperationError::FileWrite {
303 path: "/test/path".to_string(),
304 };
305 let copy_error = FileOperationError::DirectoryCreation {
306 path: "/test/dir".to_string(),
307 };
308 let file_copy_error = FileOperationError::FileCopy {
309 source_path: "/src/file".to_string(),
310 dest_path: "/dst/file".to_string(),
311 };
312
313 let write_debug = format!("{write_error:?}");
314 let copy_debug = format!("{copy_error:?}");
315 let file_copy_debug = format!("{file_copy_error:?}");
316
317 assert!(write_debug.contains("FileWrite"));
318 assert!(copy_debug.contains("DirectoryCreation"));
319 assert!(file_copy_debug.contains("FileCopy"));
320 }
321 }
322}