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