1crate::ix!();
3
4#[derive(Builder,Getters,Clone)]
6#[getset(get="pub")]
7#[builder(setter(into,strip_option))]
8pub struct BatchFileTriple {
9 index: BatchIndex,
10
11 #[builder(default)]
12 input: Option<PathBuf>,
13
14 #[builder(default)]
15 output: Option<PathBuf>,
16
17 #[builder(default)]
18 error: Option<PathBuf>,
19
20 #[builder(default)]
21 associated_metadata: Option<PathBuf>,
22
23 workspace: Arc<dyn BatchWorkspaceInterface>,
24}
25
26unsafe impl Send for BatchFileTriple {}
27unsafe impl Sync for BatchFileTriple {}
28
29impl Debug for BatchFileTriple {
30
31 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
32 f.debug_struct("BatchFileTriple")
33 .field("index", &self.index)
34 .field("input", &self.input)
35 .field("output", &self.output)
36 .field("error", &self.error)
37 .field("associated_metadata", &self.associated_metadata)
38 .finish()
39 }
40}
41
42impl PartialOrd for BatchFileTriple {
43
44 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
45 self.index.partial_cmp(&other.index)
46 }
47}
48
49impl PartialEq for BatchFileTriple {
50
51 fn eq(&self, other: &BatchFileTriple) -> bool {
52 self.index.eq(&other.index)
53 &&
54 self.input.eq(&other.input)
55 &&
56 self.output.eq(&other.output)
57 &&
58 self.error.eq(&other.error)
59 &&
60 self.associated_metadata.eq(&other.associated_metadata)
61 }
62}
63
64impl Eq for BatchFileTriple {}
65
66impl Ord for BatchFileTriple {
67
68 fn cmp(&self, other: &Self) -> Ordering {
69 self.index.cmp(&other.index)
70 }
71}
72
73impl BatchFileTriple {
74 pub fn new_for_test_with_workspace(workspace: Arc<dyn BatchWorkspaceInterface>) -> Self {
77 trace!("Constructing a test triple with a custom workspace only");
78 let index = BatchIndex::Usize(9999);
79 Self::new_direct(&index, None, None, None, None, workspace)
80 }
81
82 pub fn new_for_test_empty() -> Self {
86 let index = BatchIndex::Usize(9999);
87 let workspace = Arc::new(MockBatchWorkspace::default());
88 Self::new_direct(&index, None, None, None, None, workspace)
89 }
90
91 pub fn set_index(&mut self, new_index: BatchIndex) {
93 self.index = new_index;
94 }
95
96 pub fn effective_input_filename(&self) -> PathBuf {
97 if let Some(path) = self.input() {
98 path.clone()
100 } else {
101 self.workspace.input_filename(&self.index)
103 }
104 }
105
106 pub fn effective_output_filename(&self) -> PathBuf {
107 if let Some(path) = self.output() {
108 path.clone()
109 } else {
110 self.workspace.output_filename(&self.index)
111 }
112 }
113
114 pub fn effective_error_filename(&self) -> PathBuf {
115 if let Some(path) = self.error() {
116 path.clone()
117 } else {
118 self.workspace.error_filename(&self.index)
119 }
120 }
121
122 pub fn effective_metadata_filename(&self) -> PathBuf {
123 if let Some(path) = self.associated_metadata() {
124 path.clone()
125 } else {
126 self.workspace.metadata_filename(&self.index)
127 }
128 }
129}
130
131impl BatchFileTriple {
132
133 pub fn new_for_test_unique(workspace: Arc<dyn BatchWorkspaceInterface>) -> Self {
134
135 let triple = BatchFileTriple::new_direct(
137 &BatchIndex::new(),
139 None, None, None, None,
140 workspace,
141 );
142
143 triple
145 }
146
147 pub fn new_for_test_with_metadata_path_unique(metadata_path: PathBuf) -> Self {
148 use std::sync::atomic::{AtomicU64, Ordering};
151 static COUNTER: AtomicU64 = AtomicU64::new(1);
152 let unique_num = COUNTER.fetch_add(1, Ordering::SeqCst);
153
154 let index = BatchIndex::Usize(unique_num as usize);
157
158 let triple = Self::new_direct(
159 &index,
160 None, None, None, Some(metadata_path.clone()),
164 std::sync::Arc::new(MockBatchWorkspace::default()), );
166 triple
167 }
168
169 delegate!{
170 to self.workspace {
171 pub fn get_done_directory(&self) -> &PathBuf;
172 }
173 }
174
175 pub fn set_output_path(&mut self, path: Option<PathBuf>) {
176 trace!("Setting 'output' path to {:?}", path);
177 self.output = path;
178 }
179
180 pub fn set_input_path(&mut self, path: Option<PathBuf>) {
181 trace!("Setting 'input' path to {:?}", path);
182 self.input = path;
183 }
184
185 pub fn set_metadata_path(&mut self, path: Option<PathBuf>) {
186 trace!("Setting 'associated_metadata' path to {:?}", path);
187 self.associated_metadata = path;
188 }
189
190 pub fn set_error_path(&mut self, path: Option<PathBuf>) {
191 trace!("Setting 'error' path to {:?}", path);
192 self.error = path;
193 }
194
195 pub fn all_are_none(&self) -> bool {
196 trace!("Checking if input, output, and error are all None for batch index={:?}", self.index);
197 self.input.is_none() && self.output.is_none() && self.error.is_none()
198 }
199
200 pub fn new_with_requests(
202 requests: &[LanguageModelBatchAPIRequest],
203 workspace: Arc<dyn BatchWorkspaceInterface>
204 ) -> Result<Self,BatchInputCreationError> {
205
206 trace!("Creating new batch triple with provided requests (count={}) in workspace={:?}", requests.len(), workspace);
207 let index = BatchIndex::new();
208
209 let batch_input_filename = workspace.input_filename(&index);
210 let batch_output_filename = workspace.output_filename(&index);
211 let batch_error_filename = workspace.error_filename(&index);
212 let batch_metadata_filename = workspace.metadata_filename(&index);
213
214 info!("Creating new batch input file at {:?} with {} requests", batch_input_filename, requests.len());
215 batch_mode_batch_scribe::create_batch_input_file(&requests,&batch_input_filename)?;
216
217 assert!(batch_input_filename.exists());
219 assert!(!batch_output_filename.exists());
220 assert!(!batch_error_filename.exists());
221 assert!(!batch_metadata_filename.exists());
222
223 Ok(Self {
224 index,
225 input: Some(batch_input_filename),
226 output: None,
227 error: None,
228 associated_metadata: None,
229 workspace,
230 })
231 }
232
233 pub fn new_direct(
234 index: &BatchIndex,
235 input: Option<PathBuf>,
236 output: Option<PathBuf>,
237 error: Option<PathBuf>,
238 associated_metadata: Option<PathBuf>,
239 workspace: Arc<dyn BatchWorkspaceInterface>
240 ) -> Self {
241 trace!(
242 "Constructing BatchFileTriple::new_direct with index={:?}, input={:?}, output={:?}, error={:?}, metadata={:?}",
243 index, input, output, error, associated_metadata
244 );
245 Self {
246 index: index.clone(),
247 input,
248 output,
249 error,
250 associated_metadata,
251 workspace
252 }
253 }
254
255 pub fn new_for_test_with_metadata_path(metadata_path: PathBuf) -> Self {
259 trace!(
260 "Constructing a test triple with just an associated metadata path: {:?}",
261 metadata_path
262 );
263
264 let index = BatchIndex::Usize(9999);
265 let workspace = Arc::new(MockBatchWorkspace::default());
266
267 Self::new_direct(
268 &index,
269 None, None, None, Some(metadata_path), workspace
274 )
275 }
276
277 pub fn new_for_test_with_in_out_err_paths(
281 workspace: Arc<dyn BatchWorkspaceInterface>,
282 input: PathBuf,
283 output: Option<PathBuf>,
284 error: Option<PathBuf>,
285 ) -> Self {
286 trace!(
287 "Constructing a test triple with input={:?}, output={:?}, error={:?}",
288 input,
289 output,
290 error
291 );
292
293 let index = BatchIndex::Usize(9999);
294
295 info!(
296 "Created new_for_test_with_in_out_err_paths triple with index={:?} in a mock workspace",
297 index
298 );
299
300 Self::new_direct(
301 &index,
302 Some(input),
303 output,
304 error,
305 None,
306 workspace,
307 )
308 }
309}
310
311#[cfg(test)]
312mod batch_file_triple_filename_accessors_exhaustive_tests {
313 use super::*;
314
315 #[traced_test]
316 fn input_filename_returns_correct_path() {
317 trace!("===== BEGIN TEST: input_filename_returns_correct_path =====");
318 let workspace = Arc::new(MockBatchWorkspace::default());
319 let triple = BatchFileTriple::new_direct(
320 &BatchIndex::new(),
321 None,None,None,None,
322 workspace.clone()
323 );
324 let path = triple.effective_input_filename();
325 debug!("Returned path: {:?}", path);
326 pretty_assert_eq!(path, workspace.input_filename(&triple.index()), "Should match workspace input filename");
327 trace!("===== END TEST: effective_input_filename_returns_correct_path =====");
328 }
329
330 #[traced_test]
331 fn output_filename_returns_correct_path() {
332 trace!("===== BEGIN TEST: output_filename_returns_correct_path =====");
333 let workspace = Arc::new(MockBatchWorkspace::default());
334 let triple = BatchFileTriple::new_direct(
335 &BatchIndex::new(),
336 None,None,None,None,
337 workspace.clone()
338 );
339 let path = triple.effective_output_filename();
340 debug!("Returned path: {:?}", path);
341 pretty_assert_eq!(path, workspace.output_filename(&triple.index()), "Should match workspace output filename");
342 trace!("===== END TEST: output_filename_returns_correct_path =====");
343 }
344
345 #[traced_test]
346 fn error_filename_returns_correct_path() {
347 trace!("===== BEGIN TEST: error_filename_returns_correct_path =====");
348 let workspace = Arc::new(MockBatchWorkspace::default());
349 let triple = BatchFileTriple::new_direct(
350 &BatchIndex::new(),
351 None,None,None,None,
352 workspace.clone()
353 );
354 let path = triple.effective_error_filename();
355 debug!("Returned path: {:?}", path);
356 pretty_assert_eq!(path, workspace.error_filename(&triple.index()), "Should match workspace error filename");
357 trace!("===== END TEST: error_filename_returns_correct_path =====");
358 }
359
360 #[traced_test]
361 fn metadata_filename_returns_correct_path() {
362 trace!("===== BEGIN TEST: metadata_filename_returns_correct_path =====");
363 let workspace = Arc::new(MockBatchWorkspace::default());
364 let triple = BatchFileTriple::new_direct(
365 &BatchIndex::new(),
366 None,None,None,None,
367 workspace.clone()
368 );
369 let path = triple.effective_metadata_filename();
370 debug!("Returned path: {:?}", path);
371 pretty_assert_eq!(path, workspace.metadata_filename(&triple.index()), "Should match workspace metadata filename");
372 trace!("===== END TEST: metadata_filename_returns_correct_path =====");
373 }
374
375 #[traced_test]
376 fn set_output_path_updates_field() {
377 trace!("===== BEGIN TEST: set_output_path_updates_field =====");
378 let workspace = Arc::new(MockBatchWorkspace::default());
379 let mut triple = BatchFileTriple::new_direct(
380 &BatchIndex::new(),
381 None,None,None,None,
382 workspace
383 );
384 let new_path = Some(PathBuf::from("test_output.json"));
385 triple.set_output_path(new_path.clone());
386 debug!("Updated triple: {:?}", triple);
387 pretty_assert_eq!(*triple.output(), new_path, "Output path should be updated");
388 trace!("===== END TEST: set_output_path_updates_field =====");
389 }
390
391 #[traced_test]
392 fn set_error_path_updates_field() {
393 trace!("===== BEGIN TEST: set_error_path_updates_field =====");
394 let workspace = Arc::new(MockBatchWorkspace::default());
395 let mut triple = BatchFileTriple::new_direct(
396 &BatchIndex::new(),
397 None,None,None,None,
398 workspace
399 );
400 let new_path = Some(PathBuf::from("test_error.json"));
401 triple.set_error_path(new_path.clone());
402 debug!("Updated triple: {:?}", triple);
403 pretty_assert_eq!(*triple.error(), new_path, "Error path should be updated");
404 trace!("===== END TEST: set_error_path_updates_field =====");
405 }
406
407 #[traced_test]
408 fn all_are_none_returns_true_when_no_paths_present() {
409 trace!("===== BEGIN TEST: all_are_none_returns_true_when_no_paths_present =====");
410 let workspace = Arc::new(MockBatchWorkspace::default());
411 let triple = BatchFileTriple::new_direct(
412 &BatchIndex::new(),
413 None,None,None,None,
414 workspace
415 );
416 debug!("Triple with all None: {:?}", triple);
417 assert!(triple.all_are_none(), "Should return true when all fields are None");
418 trace!("===== END TEST: all_are_none_returns_true_when_no_paths_present =====");
419 }
420
421 #[traced_test]
422 fn all_are_none_returns_false_when_any_path_present() {
423 trace!("===== BEGIN TEST: all_are_none_returns_false_when_any_path_present =====");
424 let workspace = Arc::new(MockBatchWorkspace::default());
425 let triple = BatchFileTriple::new_direct(
426 &BatchIndex::new(),
427 Some(PathBuf::from("some_input.json")),
428 None,None,None,
429 workspace
430 );
431 debug!("Triple with input path: {:?}", triple);
432 assert!(!triple.all_are_none(), "Should return false when any field is present");
433 trace!("===== END TEST: all_are_none_returns_false_when_any_path_present =====");
434 }
435
436 #[traced_test]
437 fn new_with_requests_creates_input_file_and_none_for_others() {
438 trace!("===== BEGIN TEST: new_with_requests_creates_input_file_and_none_for_others =====");
439 let workspace = Arc::new(MockBatchWorkspace::default());
440 let requests = vec![LanguageModelBatchAPIRequest::mock("req-1")];
441
442 let triple_res = BatchFileTriple::new_with_requests(&requests, workspace.clone());
443 debug!("Resulting triple: {:?}", triple_res);
444 assert!(triple_res.is_ok(), "Should succeed in creating new batch file triple");
445 let triple = triple_res.unwrap();
446
447 assert!(triple.input().is_some(), "Input should not be None");
449 assert!(triple.output().is_none(), "Output should be None initially");
450 assert!(triple.error().is_none(), "Error should be None initially");
451
452 trace!("===== END TEST: new_with_requests_creates_input_file_and_none_for_others =====");
453 }
454
455 #[traced_test]
456 fn new_with_requests_fails_if_input_cannot_be_created() {
457 trace!("===== BEGIN TEST: new_with_requests_fails_if_input_cannot_be_created =====");
458 let workspace = Arc::new(FailingWorkspace {});
460 let requests = vec![LanguageModelBatchAPIRequest::mock("req-2")];
461
462 let triple_res = BatchFileTriple::new_with_requests(&requests, workspace);
463 debug!("Resulting triple: {:?}", triple_res);
464 assert!(triple_res.is_err(), "Should fail when input file can't be created");
465
466 trace!("===== END TEST: new_with_requests_fails_if_input_cannot_be_created =====");
467 }
468
469 #[traced_test]
470 fn new_direct_sets_all_fields_as_provided() {
471 trace!("===== BEGIN TEST: new_direct_sets_all_fields_as_provided =====");
472 let index = BatchIndex::new();
473 let input = Some(PathBuf::from("input.json"));
474 let output = Some(PathBuf::from("output.json"));
475 let error = Some(PathBuf::from("error.json"));
476 let metadata = Some(PathBuf::from("metadata.json"));
477
478 let workspace = Arc::new(MockBatchWorkspace::default());
479 let triple = BatchFileTriple::new_direct(
480 &index,
481 input.clone(), output.clone(), error.clone(), metadata.clone(),
482 workspace
483 );
484 debug!("Constructed triple: {:?}", triple);
485
486 pretty_assert_eq!(triple.index(), &index, "Index should match");
487 pretty_assert_eq!(*triple.input(), input, "Input path mismatch");
488 pretty_assert_eq!(*triple.output(), output, "Output path mismatch");
489 pretty_assert_eq!(*triple.error(), error, "Error path mismatch");
490 pretty_assert_eq!(*triple.associated_metadata(), metadata, "Metadata path mismatch");
491 trace!("===== END TEST: new_direct_sets_all_fields_as_provided =====");
492 }
493
494 #[traced_test]
495 fn batch_file_triple_partial_eq_and_ord_work_as_expected() {
496 trace!("===== BEGIN TEST: batch_file_triple_partial_eq_and_ord_work_as_expected =====");
497 let idx1 = BatchIndex::new();
498 let idx2 = BatchIndex::new();
499
500 let triple1 = BatchFileTriple::new_direct(
501 &idx1,
502 None, None, None, None,
503 Arc::new(MockBatchWorkspace::default())
504 );
505 let triple2 = BatchFileTriple::new_direct(
506 &idx2,
507 None, None, None, None,
508 Arc::new(MockBatchWorkspace::default())
509 );
510
511 assert_ne!(triple1, triple2, "Distinct indexes should not be equal");
513
514 let ordering = triple1.cmp(&triple2);
516 debug!("Ordering result: {:?}", ordering);
517 assert!(
518 ordering == std::cmp::Ordering::Less
519 || ordering == std::cmp::Ordering::Greater,
520 "They should have a total order"
521 );
522
523 trace!("===== END TEST: batch_file_triple_partial_eq_and_ord_work_as_expected =====");
524 }
525}