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    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    /// A convenience constructor for tests that supply a custom workspace. 
75    /// Everything else is None, and we assign a dummy index.
76    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    /// Some tests referred to “new_for_test_empty()”. We define it here 
83    /// as a convenience constructor that sets everything to None, 
84    /// with a dummy index and a MockBatchWorkspace.
85    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    /// Some tests set the index after constructing. We add a trivial setter:
92    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            // If the user/test code explicitly set the input path, use it
99            path.clone()
100        } else {
101            // Otherwise, fall back to workspace
102            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        // Now build the triple, but override the “index” or “output filename” with something unique:
136        let triple = BatchFileTriple::new_direct(
137            // Or pick some new function signature. For now, we pass a mocked index:
138            &BatchIndex::new(/*this is random uuid4 */),
139            None, None, None, None,
140            workspace,
141        );
142        
143        // If you prefer, also set triple metadata path, etc. 
144        triple
145    }
146
147    pub fn new_for_test_with_metadata_path_unique(metadata_path: PathBuf) -> Self {
148        // Any random generator or unique ID logic. We'll just do a
149        // thread‐local counter or random number for demonstration:
150        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        // Then we create an index with that unique number, so the default
155        // filenames become "mock_error_{unique_num}.json" etc.
156        let index = BatchIndex::Usize(unique_num as usize);
157
158        let triple = Self::new_direct(
159            &index,
160            None, // no forced input path
161            None, // no forced output path
162            None, // no forced error path
163            Some(metadata_path.clone()),
164            std::sync::Arc::new(MockBatchWorkspace::default()), // or however you handle workspace
165        );
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    //--------------------------------------------
201    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        // dev-only checks
218        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    /// A convenience constructor used by certain unit tests that only need
256    /// to set `associated_metadata` while leaving other paths as None.
257    /// We assign a dummy `BatchIndex` and a default MockBatchWorkspace (or any real workspace).
258    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,                 // no input file
270            None,                 // no output file
271            None,                 // no error file
272            Some(metadata_path),  // test sets an associated metadata path
273            workspace
274        )
275    }
276
277    /// A convenience constructor used by certain unit tests that need to set
278    /// specific input, output, and error paths directly (often to temp files).
279    /// We assign a dummy `BatchIndex` and a default MockBatchWorkspace.
280    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        // Confirm the input file is set and presumably exists in the mock
448        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        // This scenario might require a custom workspace that fails file creation.
459        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        // Equality vs difference
512        assert_ne!(triple1, triple2, "Distinct indexes should not be equal");
513        
514        // Ordering checks (the actual ordering depends on the underlying BatchIndex logic)
515        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}