Skip to main content

rust_serv/file_service/
file_service.rs

1use crate::error::Result;
2use std::fs;
3use std::path::Path;
4
5/// File metadata
6#[derive(Debug, Clone)]
7pub struct FileMetadata {
8    pub path: String,
9    pub name: String,
10    pub size: u64,
11    pub is_dir: bool,
12}
13
14/// File service for reading files and directories
15pub struct FileService;
16
17impl FileService {
18    /// Read a file's content
19    pub fn read_file(path: &Path) -> Result<Vec<u8>> {
20        if !path.exists() {
21            return Err(crate::error::Error::NotFound(path.display().to_string()));
22        }
23        fs::read(path).map_err(Into::into)
24    }
25
26    /// Check if path is a directory
27    pub fn is_directory(path: &Path) -> bool {
28        path.is_dir()
29    }
30
31    /// List directory contents
32    pub fn list_directory(path: &Path) -> Result<Vec<FileMetadata>> {
33        let entries = fs::read_dir(path)?;
34        let mut files = Vec::new();
35
36        for entry in entries {
37            let entry = entry?;
38            let metadata = entry.metadata()?;
39
40            let name = entry.file_name().to_string_lossy().to_string();
41
42            // Skip hidden files
43            if name.starts_with('.') {
44                continue;
45            }
46
47            files.push(FileMetadata {
48                path: entry.path().display().to_string(),
49                name,
50                size: metadata.len(),
51                is_dir: metadata.is_dir(),
52            });
53        }
54
55        files.sort_by(|a, b| {
56            // Directories first
57            if a.is_dir && !b.is_dir {
58                return std::cmp::Ordering::Less;
59            }
60            if !a.is_dir && b.is_dir {
61                return std::cmp::Ordering::Greater;
62            }
63            // Then alphabetically
64            a.name.cmp(&b.name)
65        });
66
67        Ok(files)
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use tempfile::TempDir;
75
76    #[test]
77    fn test_read_file() {
78        let temp_dir = TempDir::new().unwrap();
79        let file_path = temp_dir.path().join("test.txt");
80        fs::write(&file_path, "hello world").unwrap();
81
82        let content = FileService::read_file(&file_path).unwrap();
83        assert_eq!(content, b"hello world");
84    }
85
86    #[test]
87    fn test_read_nonexistent_file() {
88        let temp_dir = TempDir::new().unwrap();
89        let file_path = temp_dir.path().join("nonexistent.txt");
90
91        let result = FileService::read_file(&file_path);
92        assert!(result.is_err());
93    }
94
95    #[test]
96    fn test_list_directory() {
97        let temp_dir = TempDir::new().unwrap();
98        fs::write(temp_dir.path().join("file1.txt"), "content1").unwrap();
99        fs::write(temp_dir.path().join("file2.txt"), "content2").unwrap();
100        fs::create_dir(temp_dir.path().join("subdir")).unwrap();
101
102        let files = FileService::list_directory(temp_dir.path()).unwrap();
103        assert_eq!(files.len(), 3);
104    }
105
106    #[test]
107    fn test_list_directory_skips_hidden() {
108        let temp_dir = TempDir::new().unwrap();
109        fs::write(temp_dir.path().join("visible.txt"), "content").unwrap();
110        fs::write(temp_dir.path().join(".hidden.txt"), "hidden").unwrap();
111
112        let files = FileService::list_directory(temp_dir.path()).unwrap();
113        assert_eq!(files.len(), 1);
114        assert_eq!(files[0].name, "visible.txt");
115    }
116
117    #[test]
118    fn test_list_directory_nonexistent() {
119        let temp_dir = TempDir::new().unwrap();
120        let nonexist_path = temp_dir.path().join("nonexistent");
121
122        let result = FileService::list_directory(&nonexist_path);
123        assert!(result.is_err());
124    }
125
126    #[test]
127    fn test_list_directory_sorts_by_type() {
128        let temp_dir = TempDir::new().unwrap();
129        fs::create_dir(temp_dir.path().join("subdir")).unwrap();
130        fs::write(temp_dir.path().join("file.txt"), "content").unwrap();
131
132        let files = FileService::list_directory(temp_dir.path()).unwrap();
133        assert_eq!(files.len(), 2);
134        assert!(files[0].is_dir);
135        assert!(!files[1].is_dir);
136    }
137
138    #[test]
139    fn test_read_file_permission_error() {
140        let result = FileService::read_file(Path::new("/root/.bashrc"));
141        // On non-root systems, this should fail
142        assert!(result.is_err());
143    }
144
145    #[test]
146    fn test_list_directory_empty() {
147        let temp_dir = TempDir::new().unwrap();
148        // Don't create any files
149
150        let files = FileService::list_directory(temp_dir.path()).unwrap();
151        assert_eq!(files.len(), 0);
152    }
153
154    #[test]
155    fn test_is_directory_true() {
156        let temp_dir = TempDir::new().unwrap();
157        let dir = temp_dir.path().join("testdir");
158        fs::create_dir(&dir).unwrap();
159
160        assert!(FileService::is_directory(&dir));
161    }
162
163    #[test]
164    fn test_is_directory_false() {
165        let temp_dir = TempDir::new().unwrap();
166        let file = temp_dir.path().join("testfile.txt");
167        fs::write(&file, "content").unwrap();
168
169        assert!(!FileService::is_directory(&file));
170    }
171
172    #[test]
173    fn test_is_directory_nonexistent() {
174        assert!(!FileService::is_directory(Path::new("/nonexistent/path")));
175    }
176}