sakurs_cli/input/
file_reader.rs

1//! File reading utilities
2
3use anyhow::{Context, Result};
4use std::fs;
5use std::path::Path;
6
7/// File reader with UTF-8 validation
8pub struct FileReader;
9
10impl FileReader {
11    /// Read a file as UTF-8 text
12    pub fn read_text(path: &Path) -> Result<String> {
13        let content = fs::read_to_string(path)
14            .with_context(|| format!("Failed to read file: {}", path.display()))?;
15
16        Ok(content)
17    }
18
19    /// Get file size in bytes
20    pub fn file_size(path: &Path) -> Result<u64> {
21        let metadata = fs::metadata(path)
22            .with_context(|| format!("Failed to get metadata for: {}", path.display()))?;
23
24        Ok(metadata.len())
25    }
26
27    /// Check if file should be processed in streaming mode based on size
28    pub fn should_stream(path: &Path, threshold_mb: u64) -> Result<bool> {
29        let size = Self::file_size(path)?;
30        Ok(size > threshold_mb * 1024 * 1024)
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use std::fs::{self, File};
38    use tempfile::TempDir;
39
40    #[test]
41    fn test_read_text_success() {
42        let temp_dir = TempDir::new().unwrap();
43        let file_path = temp_dir.path().join("test.txt");
44
45        let content = "Hello, world!\nThis is a test.";
46        fs::write(&file_path, content).unwrap();
47
48        let result = FileReader::read_text(&file_path).unwrap();
49        assert_eq!(result, content);
50    }
51
52    #[test]
53    fn test_read_text_nonexistent_file() {
54        let path = Path::new("/nonexistent/file.txt");
55        let result = FileReader::read_text(path);
56
57        assert!(result.is_err());
58        let err_msg = result.unwrap_err().to_string();
59        assert!(err_msg.contains("Failed to read file"));
60    }
61
62    #[test]
63    fn test_read_text_utf8_content() {
64        let temp_dir = TempDir::new().unwrap();
65        let file_path = temp_dir.path().join("utf8.txt");
66
67        let content = "Hello δΈ–η•Œ! 🌍 Emoji and UTF-8";
68        fs::write(&file_path, content).unwrap();
69
70        let result = FileReader::read_text(&file_path).unwrap();
71        assert_eq!(result, content);
72    }
73
74    #[test]
75    fn test_file_size() {
76        let temp_dir = TempDir::new().unwrap();
77        let file_path = temp_dir.path().join("sized.txt");
78
79        let content = "a".repeat(1024);
80        fs::write(&file_path, &content).unwrap();
81
82        let size = FileReader::file_size(&file_path).unwrap();
83        assert_eq!(size, 1024);
84    }
85
86    #[test]
87    fn test_file_size_nonexistent() {
88        let path = Path::new("/nonexistent/file.txt");
89        let result = FileReader::file_size(path);
90
91        assert!(result.is_err());
92        let err_msg = result.unwrap_err().to_string();
93        assert!(err_msg.contains("Failed to get metadata"));
94    }
95
96    #[test]
97    fn test_should_stream_small_file() {
98        let temp_dir = TempDir::new().unwrap();
99        let file_path = temp_dir.path().join("small.txt");
100
101        fs::write(&file_path, "small content").unwrap();
102
103        let should_stream = FileReader::should_stream(&file_path, 1).unwrap();
104        assert!(!should_stream);
105    }
106
107    #[test]
108    fn test_should_stream_large_file() {
109        let temp_dir = TempDir::new().unwrap();
110        let file_path = temp_dir.path().join("large.txt");
111
112        // Create a file larger than 1KB
113        let content = "a".repeat(2048);
114        fs::write(&file_path, content).unwrap();
115
116        // Threshold is 0.001 MB (1KB)
117        let should_stream = FileReader::should_stream(&file_path, 0).unwrap();
118        assert!(should_stream);
119    }
120
121    #[test]
122    fn test_empty_file() {
123        let temp_dir = TempDir::new().unwrap();
124        let file_path = temp_dir.path().join("empty.txt");
125
126        File::create(&file_path).unwrap();
127
128        let content = FileReader::read_text(&file_path).unwrap();
129        assert_eq!(content, "");
130
131        let size = FileReader::file_size(&file_path).unwrap();
132        assert_eq!(size, 0);
133    }
134
135    #[cfg(unix)]
136    #[test]
137    fn test_read_text_permission_denied() {
138        use std::os::unix::fs::PermissionsExt;
139
140        let temp_dir = TempDir::new().unwrap();
141        let file_path = temp_dir.path().join("no_read.txt");
142
143        fs::write(&file_path, "content").unwrap();
144
145        // Remove read permissions
146        let metadata = fs::metadata(&file_path).unwrap();
147        let mut permissions = metadata.permissions();
148        permissions.set_mode(0o000);
149        fs::set_permissions(&file_path, permissions).unwrap();
150
151        let result = FileReader::read_text(&file_path);
152        assert!(result.is_err());
153
154        // Restore permissions for cleanup
155        let mut permissions = fs::metadata(&file_path).unwrap().permissions();
156        permissions.set_mode(0o644);
157        fs::set_permissions(&file_path, permissions).unwrap();
158    }
159}