workspacer_crate/
read_file_string.rs1crate::ix!();
3
4#[async_trait]
5impl ReadFileString for CrateHandle {
6 async fn read_file_string(&self, path: &Path) -> Result<String, CrateError> {
7 let mut full_path = path.to_path_buf();
12
13 if !full_path.is_absolute() {
15 let crate_str = self.crate_path().to_string_lossy().to_string();
19 let path_str = full_path.to_string_lossy().to_string();
20
21 if path_str.starts_with(&crate_str) {
22 debug!("Path is already under crate_path: {}", path_str);
24 } else {
25 full_path = self.crate_path().join(path_str);
27 }
28 }
29
30 let content_result = fs::read_to_string(&full_path).await;
31 content_result.map_err(|io_err| CrateError::IoError {
32 io_error: Arc::new(io_err),
33 context: format!("Failed to read file: {}", full_path.display()),
34 })
35 }
36}
37
38#[cfg(test)]
39mod test_read_file_string {
40 use super::*;
41 use std::path::{Path, PathBuf};
42 use tempfile::tempdir;
43 use tokio::fs::{create_dir_all, File};
44 use tokio::io::AsyncWriteExt;
45 use std::io::Write;
46
47
48 #[derive(Clone)]
53 struct MockCratePath(PathBuf);
54
55 impl AsRef<Path> for MockCratePath {
56 fn as_ref(&self) -> &Path {
57 &self.0
58 }
59 }
60
61 async fn setup_crate_handle() -> CrateHandle {
65 let tmp_dir = tempdir().expect("Failed to create temp dir");
67 let crate_root = tmp_dir.path().to_path_buf();
68
69 let cargo_toml_content = r#"
71 [package]
72 name = "mock_crate"
73 version = "0.1.0"
74 authors = ["Test <test@example.com>"]
75 license = "MIT"
76 "#;
77 let cargo_toml_path = crate_root.join("Cargo.toml");
78
79 {
80 let mut f = File::create(&cargo_toml_path)
81 .await
82 .expect("Failed to create Cargo.toml");
83 f.write_all(cargo_toml_content.as_bytes())
84 .await
85 .expect("Failed to write Cargo.toml");
86 }
87
88 let mock_path = MockCratePath(crate_root);
90 CrateHandle::new(&mock_path)
91 .await
92 .expect("Failed to create CrateHandle")
93 }
94
95 async fn write_file_in_crate(handle: &CrateHandle, relative_path: &str, content: &str) -> PathBuf {
99 let file_path = handle.as_ref().join(relative_path);
100 if let Some(parent) = file_path.parent() {
101 create_dir_all(parent)
102 .await
103 .expect("Failed to create parent directories");
104 }
105 let mut f = File::create(&file_path)
106 .await
107 .expect("Failed to create test file");
108 f.write_all(content.as_bytes())
109 .await
110 .expect("Failed to write test file content");
111 file_path
112 }
113
114 #[tokio::test]
120 async fn test_read_file_string_absolute_path() {
121 let handle = setup_crate_handle().await;
122 let outside_tmp_dir = tempdir().expect("Failed to create second temp dir");
125 let outside_file_path = outside_tmp_dir.path().join("external_file.txt");
126
127 {
128 let mut f = std::fs::File::create(&outside_file_path)
129 .expect("Failed to create external file");
130 writeln!(f, "This is outside the crate path.").expect("Write failed");
131 }
132
133 let content = handle
134 .read_file_string(&outside_file_path)
135 .await
136 .expect("Failed to read external file with absolute path");
137 assert_eq!(
138 content.trim(),
139 "This is outside the crate path.",
140 "Should read from the absolute path directly"
141 );
142 }
143
144 #[tokio::test]
147 async fn test_read_file_string_relative_already_prefixed() {
148 let handle = setup_crate_handle().await;
149
150 let relative_path_str = "nested/hello.txt";
152 let file_path_in_crate = write_file_in_crate(&handle, relative_path_str, "Hello, World").await;
153
154 let path_str = file_path_in_crate.to_string_lossy().into_owned();
158 let content = handle
163 .read_file_string(Path::new(&path_str))
164 .await
165 .expect("Failed to read file with 'already prefixed' path");
166 assert_eq!(content, "Hello, World", "Should read the same content");
167 }
168
169 #[tokio::test]
172 async fn test_read_file_string_relative_joined() {
173 let handle = setup_crate_handle().await;
174
175 let file_path = write_file_in_crate(&handle, "src/myfile.txt", "some data").await;
177
178 let relative_path = Path::new("src/myfile.txt");
180 let content = handle
181 .read_file_string(relative_path)
182 .await
183 .expect("Failed to read file with relative path that does not match prefix");
184 assert_eq!(content, "some data");
185 }
186
187 #[tokio::test]
189 async fn test_read_file_string_missing_file() {
190 let handle = setup_crate_handle().await;
191 let missing_path = Path::new("this_file_does_not_exist.txt");
192 let result = handle.read_file_string(missing_path).await;
193
194 assert!(result.is_err(), "Expected error for missing file");
195 match result {
196 Err(CrateError::IoError { context, .. }) => {
197 assert!(
199 context.contains("Failed to read file"),
200 "Error context should mention failed to read file"
201 );
202 assert!(
204 context.contains("this_file_does_not_exist.txt"),
205 "Error context should mention the missing file"
206 );
207 }
208 other => panic!("Expected CrateError::IoError, got: {other:?}"),
209 }
210 }
211
212 #[tokio::test]
215 async fn test_read_file_string_same_crate_path_but_absolute() {
216 let handle = setup_crate_handle().await;
217 let relative_path = "docs/some_doc.txt";
219 let file_path = write_file_in_crate(&handle, relative_path, "doc content").await;
220 let absolute_path = file_path.canonicalize().expect("Failed to canonicalize path");
221
222 let content = handle
224 .read_file_string(&absolute_path)
225 .await
226 .expect("Failed to read doc content with absolute path");
227 assert_eq!(content, "doc content");
228 }
229
230 #[tokio::test]
235 async fn test_read_file_string_partial_prefix() {
236 let handle = setup_crate_handle().await;
237 let crate_str = handle.crate_path().to_string_lossy().to_string();
238
239 let pathological_path_str = format!("xxx{}yyy", crate_str);
242
243 let result = handle
245 .read_file_string(Path::new(&pathological_path_str))
246 .await;
247
248 assert!(result.is_err(), "Likely fails to find file or fails to parse path");
251 }
252}