batch_mode_batch_triple/
batch_file_triple.rs

1// ---------------- [ File: batch-mode-batch-triple/src/batch_file_triple.rs ]
2crate::ix!();
3
4/// Represents the batch files associated with a specific index.
5#[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    /// A convenience constructor for tests that supply a custom workspace. 
81    /// Everything else is None, and we assign a dummy index.
82    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    /// Some tests referred to “new_for_test_empty()”. We define it here 
89    /// as a convenience constructor that sets everything to None, 
90    /// with a dummy index and a MockBatchWorkspace.
91    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    /// Some tests set the index after constructing. We add a trivial setter:
98    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            // If the user/test code explicitly set the input path, use it
105            path.clone()
106        } else {
107            // Otherwise, fall back to workspace
108            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        // Now build the triple, but override the “index” or “output filename” with something unique:
150        let triple = BatchFileTriple::new_direct(
151            // Or pick some new function signature. For now, we pass a mocked index:
152            &BatchIndex::new(/*this is random uuid4 */),
153            None, None, None, None, None,
154            workspace,
155        );
156        
157        // If you prefer, also set triple metadata path, etc. 
158        triple
159    }
160
161    pub fn new_for_test_with_metadata_path_unique(metadata_path: PathBuf) -> Self {
162        // Any random generator or unique ID logic. We'll just do a
163        // thread‐local counter or random number for demonstration:
164        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        // Then we create an index with that unique number, so the default
169        // filenames become "mock_error_{unique_num}.json" etc.
170        let index = BatchIndex::Usize(unique_num as usize);
171
172        let triple = Self::new_direct(
173            &index,
174            None, // no forced input path
175            None, // no forced output path
176            None, // no forced error path
177            Some(metadata_path.clone()),
178            None,
179            std::sync::Arc::new(MockBatchWorkspace::default()), // or however you handle workspace
180        );
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    //--------------------------------------------
221    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        // dev-only checks
244        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    /// A convenience constructor used by certain unit tests that only need
286    /// to set `associated_metadata` while leaving other paths as None.
287    /// We assign a dummy `BatchIndex` and a default MockBatchWorkspace (or any real workspace).
288    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,                 // no input file
300            None,                 // no output file
301            None,                 // no error file
302            Some(metadata_path),  // test sets an associated metadata path
303            None,
304            workspace
305        )
306    }
307
308    /// A convenience constructor used by certain unit tests that need to set
309    /// specific input, output, and error paths directly (often to temp files).
310    /// We assign a dummy `BatchIndex` and a default MockBatchWorkspace.
311    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        // Confirm the input file is set and presumably exists in the mock
480        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        // This scenario might require a custom workspace that fails file creation.
491        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        // Equality vs difference
545        assert_ne!(triple1, triple2, "Distinct indexes should not be equal");
546        
547        // Ordering checks (the actual ordering depends on the underlying BatchIndex logic)
548        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}