batch_mode_batch_client/
download_error_file.rs1crate::ix!();
3
4#[async_trait]
5impl<E> DownloadErrorFile<E> for BatchFileTriple
6where
7 E: From<BatchDownloadError>
8 + From<std::io::Error>
9 + From<BatchMetadataError>
10 + From<OpenAIClientError>
11 + Debug,
12{
13 async fn download_error_file(
14 &mut self,
15 client: &dyn LanguageModelClientInterface<E>,
16 ) -> Result<(), E> {
17 info!("downloading batch error file");
18
19 if let Some(err_path) = &self.error() {
22 if err_path.exists() {
23 warn!(
24 "Error file already present on disk at path={:?}. \
25 Aborting to avoid overwriting.",
26 err_path
27 );
28 return Err(BatchDownloadError::ErrorFileAlreadyExists {
29 triple: self.clone(),
30 }
31 .into());
32 }
33 }
34
35 let metadata_filename = match self.associated_metadata() {
36 Some(file) => file.to_path_buf(),
37 None => self.effective_metadata_filename().to_path_buf(),
38 };
39 debug!("Using metadata file for error: {:?}", metadata_filename);
40
41 let metadata = BatchMetadata::load_from_file(&metadata_filename).await?;
42 let error_file_id = metadata.error_file_id()?;
43
44 let file_content = client.file_content(error_file_id).await?;
45
46 let error_path = self.effective_error_filename();
47 if let Some(parent) = error_path.parent() {
48 tokio::fs::create_dir_all(parent).await.ok();
49 }
50
51 if error_path.exists() {
53 std::fs::remove_file(&error_path)?;
54 }
55
56 std::fs::write(&error_path, file_content)?;
57 self.set_error_path(Some(error_path));
58 Ok(())
59 }
60}
61
62#[cfg(test)]
63mod download_error_file_tests {
64 use super::*;
65 use futures::executor::block_on;
66 use std::fs;
67 use tempfile::tempdir;
68 use tracing::{debug, error, info, trace, warn};
69
70 #[traced_test]
73 async fn test_download_error_file_ok() {
74 info!("Beginning test_download_error_file_ok");
75 trace!("Constructing mock client...");
76 let mock_client = MockLanguageModelClientBuilder::<MockBatchClientError>::default()
77 .build()
78 .unwrap();
79 debug!("Mock client: {:?}", mock_client);
80
81 let error_file_id = "some_error_file_id";
83 {
84 let mut files_guard = mock_client.files().write().unwrap();
85 files_guard.insert(error_file_id.to_string(), Bytes::from("mock error contents"));
86 }
87
88 let tmpdir = tempdir().unwrap();
90 let metadata_path = tmpdir.path().join("metadata.json");
91 let metadata = BatchMetadataBuilder::default()
92 .batch_id("some_batch_id".to_string())
93 .input_file_id("some_input_file_id".to_string())
94 .output_file_id(None)
95 .error_file_id(Some(error_file_id.to_string()))
96 .build()
97 .unwrap();
98 info!("Saving metadata at {:?}", metadata_path);
99 metadata.save_to_file(&metadata_path).await.unwrap();
100
101 trace!("Creating BatchFileTriple with known metadata path...");
102 let mut triple = BatchFileTriple::new_for_test_with_metadata_path(metadata_path.clone());
103 triple.set_metadata_path(Some(metadata_path.clone()));
104
105 let err_path = tmpdir.path().join("error.json");
107 triple.set_error_path(Some(err_path.clone()));
108
109 trace!("Calling download_error_file...");
110 let result = triple.download_error_file(&mock_client).await;
111 debug!("Result from download_error_file: {:?}", result);
112
113 assert!(result.is_ok(), "Should succeed for a valid error file");
114 let contents = fs::read_to_string(&err_path).unwrap();
116 pretty_assert_eq!(contents, "mock error contents");
117
118 info!("test_download_error_file_ok passed");
119 }
120
121 #[traced_test]
122 async fn test_download_error_file_already_exists() {
123 info!("Beginning test_download_error_file_already_exists");
124 let mock_client = MockLanguageModelClientBuilder::<MockBatchClientError>::default()
125 .build()
126 .unwrap();
127 debug!("Mock client: {:?}", mock_client);
128
129 let tmpdir = tempdir().unwrap();
131 let metadata_path = tmpdir.path().join("metadata.json");
132 let metadata = BatchMetadataBuilder::default()
133 .batch_id("batch_id_exists_err")
134 .input_file_id("some_input_file_id".to_string())
135 .output_file_id(None)
136 .error_file_id(Some("already_exists_err_file_id".to_string()))
137 .build()
138 .unwrap();
139 metadata.save_to_file(&metadata_path).await.unwrap();
140
141 let mut triple = BatchFileTriple::new_for_test_with_metadata_path(metadata_path.clone());
143 triple.set_metadata_path(Some(metadata_path.clone()));
144
145 let existing_err_path = tmpdir.path().join("error.json");
147 fs::write(&existing_err_path, b"existing content").unwrap();
148 triple.set_error_path(Some(existing_err_path.clone()));
149
150 let result = triple.download_error_file(&mock_client).await;
151 debug!("Result from download_error_file: {:?}", result);
152
153 assert!(
154 result.is_err(),
155 "Should fail if error file already exists on disk"
156 );
157 info!("test_download_error_file_already_exists passed");
158 }
159
160 #[traced_test]
161 async fn test_download_error_file_missing_error_file_id() {
162 info!("Beginning test_download_error_file_missing_error_file_id");
163 let mock_client = MockLanguageModelClientBuilder::<MockBatchClientError>::default()
164 .build()
165 .unwrap();
166 debug!("Mock client: {:?}", mock_client);
167
168 let tmpdir = tempdir().unwrap();
170 let metadata_path = tmpdir.path().join("metadata.json");
171 let metadata = BatchMetadataBuilder::default()
172 .batch_id("batch_no_err_id")
173 .input_file_id("input_file_id".to_string())
174 .output_file_id(None)
175 .error_file_id(None)
176 .build()
177 .unwrap();
178 metadata.save_to_file(&metadata_path).await.unwrap();
179
180 let mut triple = BatchFileTriple::new_for_test_with_metadata_path(metadata_path.clone());
182 triple.set_metadata_path(Some(metadata_path.clone()));
183
184 let err_path = tmpdir.path().join("placeholder_error_file.json");
186 triple.set_error_path(Some(err_path.clone()));
187
188 let result = triple.download_error_file(&mock_client).await;
189 debug!("Result from download_error_file: {:?}", result);
190
191 assert!(
192 result.is_err(),
193 "Should fail if error_file_id is not present in metadata"
194 );
195 info!("test_download_error_file_missing_error_file_id passed");
196 }
197
198 #[traced_test]
199 async fn test_download_error_file_client_file_not_found() {
200 info!("Beginning test_download_error_file_client_file_not_found");
201 let mock_client = MockLanguageModelClientBuilder::<MockBatchClientError>::default()
202 .build()
203 .unwrap();
204
205 let tmpdir = tempdir().unwrap();
207 let metadata_path = tmpdir.path().join("metadata.json");
208 let metadata = BatchMetadataBuilder::default()
209 .batch_id("batch_err_file_not_found")
210 .input_file_id("some_input".to_string())
211 .output_file_id(None)
212 .error_file_id(Some("err_file_that_does_not_exist".to_string()))
213 .build()
214 .unwrap();
215 metadata.save_to_file(&metadata_path).await.unwrap();
216
217 let mut triple = BatchFileTriple::new_for_test_with_metadata_path(metadata_path.clone());
218 triple.set_metadata_path(Some(metadata_path.clone()));
219
220 let err_path = tmpdir.path().join("error_file.json");
221 triple.set_error_path(Some(err_path.clone()));
222
223 let result = triple.download_error_file(&mock_client).await;
224 debug!("Result from download_error_file: {:?}", result);
225
226 assert!(
227 result.is_err(),
228 "Should fail if the mock client cannot find the error file_id"
229 );
230 info!("test_download_error_file_client_file_not_found passed");
231 }
232
233 #[traced_test]
234 async fn test_download_error_file_io_write_error() {
235 info!("Beginning test_download_error_file_io_write_error");
236 let mock_client = MockLanguageModelClientBuilder::<MockBatchClientError>::default()
237 .build()
238 .unwrap();
239
240 let error_file_id = "some_err_file_id_for_io_error";
242 {
243 let mut files_guard = mock_client.files().write().unwrap();
244 files_guard.insert(error_file_id.to_string(), Bytes::from("err content"));
245 }
246
247 let tmpdir_meta = tempdir().unwrap();
250 let tmpdir_readonly = tempdir().unwrap();
251
252 let metadata_path = tmpdir_meta.path().join("metadata.json");
254 let metadata = BatchMetadataBuilder::default()
255 .batch_id("batch_io_error")
256 .input_file_id("some_input".to_string())
257 .output_file_id(None)
258 .error_file_id(Some(error_file_id.to_string()))
259 .build()
260 .unwrap();
261 info!("Saving metadata at {:?}", metadata_path);
262 metadata.save_to_file(&metadata_path).await.unwrap();
263 debug!("Metadata file created successfully.");
264
265 let mut triple = BatchFileTriple::new_for_test_with_metadata_path(metadata_path.clone());
267 triple.set_metadata_path(Some(metadata_path.clone()));
268
269 let err_path = tmpdir_readonly.path().join("error.json");
271 triple.set_error_path(Some(err_path.clone()));
272
273 let mut perms = fs::metadata(tmpdir_readonly.path()).unwrap().permissions();
275 perms.set_readonly(true);
276 fs::set_permissions(tmpdir_readonly.path(), perms).unwrap();
277
278 let result = triple.download_error_file(&mock_client).await;
279 debug!("Result from download_error_file: {:?}", result);
280
281 let mut perms = fs::metadata(tmpdir_readonly.path()).unwrap().permissions();
283 perms.set_readonly(false);
284 fs::set_permissions(tmpdir_readonly.path(), perms).unwrap();
285
286 assert!(
287 result.is_err(),
288 "Should fail with an I/O error when the directory is read-only"
289 );
290 info!("test_download_error_file_io_write_error passed");
291 }
292}