batch_mode_batch_reconciliation/
batch_file_reconciliation_recommended_course_of_action.rs

1// ---------------- [ File: batch-mode-batch-reconciliation/src/batch_file_reconciliation_recommended_course_of_action.rs ]
2crate::ix!();
3
4#[derive(Debug,Clone,PartialEq,Eq)]
5pub struct BatchFileReconciliationRecommendedCourseOfAction {
6    steps: Vec<BatchFileTripleReconciliationOperation>,
7}
8
9impl BatchFileReconciliationRecommendedCourseOfAction {
10    pub fn steps(&self) -> &[BatchFileTripleReconciliationOperation] {
11        &self.steps
12    }
13}
14
15impl From<Vec<BatchFileTripleReconciliationOperation>> for BatchFileReconciliationRecommendedCourseOfAction {
16
17    fn from(steps: Vec<BatchFileTripleReconciliationOperation>) -> Self {
18        Self { steps }
19    }
20}
21
22impl TryFrom<&BatchFileTriple> for BatchFileReconciliationRecommendedCourseOfAction {
23
24    type Error = BatchReconciliationError;
25
26    // here we are assuming the results of each step are a success and that something happened
27    //
28    // in case we get an error, for now the course of action will be to log the error and skip
29    // processing this batch triple.
30    //
31    fn try_from(triple: &BatchFileTriple) -> Result<BatchFileReconciliationRecommendedCourseOfAction, BatchReconciliationError> {
32        if triple.all_are_none() {
33            return Err(BatchWorkspaceError::NoBatchFileTripleAtIndex {
34                index: triple.index().clone(),
35            }.into());
36        }
37
38        if triple.input().is_none() {
39            return Err(BatchReconciliationError::MissingBatchInputFileButOthersExist {
40                index: triple.index().clone(),
41                output: triple.output().clone(),
42                error: triple.error().clone(),
43            });
44        }
45
46        use BatchFileTripleReconciliationOperation::*;
47        let steps = match BatchFileState::from(triple) {
48            BatchFileState::InputOutputError => {
49                warn!("Both output and error files are present for batch {:?}", triple.index());
50                vec![
51                    EnsureInputRequestIdsMatchOutputRequestIdsCombinedWithErrorRequestIds,
52                    ProcessBatchErrorFile,
53                    ProcessBatchOutputFile,
54                    MoveBatchTripleToTheDoneDirectory,
55                ]
56            }
57            BatchFileState::InputOutput => {
58                vec![
59                    EnsureInputRequestIdsMatchOutputRequestIds,
60                    ProcessBatchOutputFile,
61                    MoveBatchInputAndOutputToTheDoneDirectory,
62                ]
63            }
64            BatchFileState::InputError => {
65                warn!("Error file present but no output file for batch {:?}", triple.index());
66                vec![
67                    EnsureInputRequestIdsMatchErrorRequestIds,
68                    ProcessBatchErrorFile,
69                    MoveBatchInputAndErrorToTheDoneDirectory,
70                ]
71
72            }
73            BatchFileState::InputOnly => {
74                warn!("Neither output nor error files are present for batch {:?}", triple.index());
75                vec![
76                    CheckForBatchOutputAndErrorFileOnline,
77                    RecalculateRecommendedCourseOfActionIfTripleChanged,
78                ]
79            }
80        };
81
82        Ok(BatchFileReconciliationRecommendedCourseOfAction::from(steps))
83    }
84}
85
86#[cfg(test)]
87mod batch_file_reconciliation_recommended_course_of_action_tests {
88    use super::*;
89
90    #[traced_test]
91    async fn test_try_from_triple_all_none() {
92
93        let mock_index = BatchIndex::from(123u64);
94
95        let workspace  = BatchWorkspace::new_temp().await.expect("expected to get our workspace") as Arc<dyn BatchWorkspaceInterface>;
96
97        let triple = BatchFileTripleBuilder::default()
98            .index(mock_index.clone())
99            .workspace(workspace)
100            .build()
101            .unwrap();
102
103        let result = BatchFileReconciliationRecommendedCourseOfAction::try_from(&triple);
104        assert!(result.is_err(), "Expected error if all files are None");
105        match result {
106            Err(BatchReconciliationError::BatchWorkspaceError(e)) => {
107                match e {
108                    BatchWorkspaceError::NoBatchFileTripleAtIndex { index } => {
109                        pretty_assert_eq!(index, mock_index);
110                    }
111                    _ => panic!("Unexpected error variant for all_none scenario"),
112                }
113            }
114            other => panic!("Unexpected result: {:?}", other),
115        }
116    }
117
118    #[traced_test]
119    async fn test_try_from_triple_missing_input_but_has_output() {
120
121        let workspace  = BatchWorkspace::new_temp().await.expect("expected to get our workspace") as Arc<dyn BatchWorkspaceInterface>;
122
123        let triple = BatchFileTripleBuilder::default()
124            .index(BatchIndex::from(9999u64))
125            .output::<PathBuf>("some_output.json".into())
126            .workspace(workspace)
127            .build()
128            .unwrap();
129
130        let result = BatchFileReconciliationRecommendedCourseOfAction::try_from(&triple);
131        assert!(result.is_err(), "Should fail if input is missing but output exists");
132        match result {
133            Err(BatchReconciliationError::MissingBatchInputFileButOthersExist { index, output, error }) => {
134                pretty_assert_eq!(index.as_u64(), Some(9999u64));
135                pretty_assert_eq!(output, Some("some_output.json".into()));
136                pretty_assert_eq!(error, None);
137            }
138            other => panic!("Unexpected error variant for missing input scenario: {:?}", other),
139        }
140    }
141
142    #[traced_test]
143    async fn test_try_from_triple_input_only() {
144
145        let workspace  = BatchWorkspace::new_temp().await.expect("expected to get our workspace") as Arc<dyn BatchWorkspaceInterface>;
146
147        let triple = BatchFileTripleBuilder::default()
148            .index(BatchIndex::from(1000u64))
149            .input::<PathBuf>("input.json".into())
150            .workspace(workspace)
151            .build()
152            .unwrap();
153
154        let result = BatchFileReconciliationRecommendedCourseOfAction::try_from(&triple);
155        assert!(result.is_ok(), "Input-only scenario should be Ok");
156        let steps = result.unwrap().steps().to_vec();
157        // Expect: [CheckForBatchOutputAndErrorFileOnline, RecalculateRecommendedCourseOfActionIfTripleChanged]
158        pretty_assert_eq!(
159            steps,
160            vec![
161                BatchFileTripleReconciliationOperation::CheckForBatchOutputAndErrorFileOnline,
162                BatchFileTripleReconciliationOperation::RecalculateRecommendedCourseOfActionIfTripleChanged
163            ]
164        );
165    }
166
167    #[traced_test]
168    async fn test_try_from_triple_input_output() {
169
170        let workspace  = BatchWorkspace::new_temp().await.expect("expected to get our workspace") as Arc<dyn BatchWorkspaceInterface>;
171
172        let triple = BatchFileTripleBuilder::default()
173            .index(BatchIndex::from(1u64))
174            .input::<PathBuf>("input.json".into())
175            .output::<PathBuf>("output.json".into())
176            .workspace(workspace)
177            .build()
178            .unwrap();
179
180
181        let result = BatchFileReconciliationRecommendedCourseOfAction::try_from(&triple);
182        assert!(result.is_ok(), "Input+Output scenario should be Ok");
183        let steps = result.unwrap().steps().to_vec();
184        // Expect: [EnsureInputRequestIdsMatchOutputRequestIds, ProcessBatchOutputFile, MoveBatchInputAndOutputToTheDoneDirectory]
185        pretty_assert_eq!(
186            steps,
187            vec![
188                BatchFileTripleReconciliationOperation::EnsureInputRequestIdsMatchOutputRequestIds,
189                BatchFileTripleReconciliationOperation::ProcessBatchOutputFile,
190                BatchFileTripleReconciliationOperation::MoveBatchInputAndOutputToTheDoneDirectory
191            ]
192        );
193    }
194
195    #[traced_test]
196    async fn test_try_from_triple_input_error() {
197
198        let workspace  = BatchWorkspace::new_temp().await.expect("expected to get our workspace") as Arc<dyn BatchWorkspaceInterface>;
199
200        let triple = BatchFileTripleBuilder::default()
201            .index(BatchIndex::from(2u64))
202            .input::<PathBuf>("input.json".into())
203            .error::<PathBuf>("error.json".into())
204            .workspace(workspace)
205            .build()
206            .unwrap();
207
208        let result = BatchFileReconciliationRecommendedCourseOfAction::try_from(&triple);
209        assert!(result.is_ok(), "Input+Error scenario should be Ok");
210        let steps = result.unwrap().steps().to_vec();
211        // Expect: [EnsureInputRequestIdsMatchErrorRequestIds, ProcessBatchErrorFile, MoveBatchInputAndErrorToTheDoneDirectory]
212        pretty_assert_eq!(
213            steps,
214            vec![
215                BatchFileTripleReconciliationOperation::EnsureInputRequestIdsMatchErrorRequestIds,
216                BatchFileTripleReconciliationOperation::ProcessBatchErrorFile,
217                BatchFileTripleReconciliationOperation::MoveBatchInputAndErrorToTheDoneDirectory
218            ]
219        );
220    }
221
222    #[traced_test]
223    async fn test_try_from_triple_input_output_error() {
224
225        let workspace  = BatchWorkspace::new_temp().await.expect("expected to get our workspace") as Arc<dyn BatchWorkspaceInterface>;
226
227        let triple = BatchFileTripleBuilder::default()
228            .index(BatchIndex::from(3u64))
229            .input::<PathBuf>("input.json".into())
230            .output::<PathBuf>("output.json".into())
231            .error::<PathBuf>("error.json".into())
232            .workspace(workspace)
233            .build()
234            .unwrap();
235
236        let result = BatchFileReconciliationRecommendedCourseOfAction::try_from(&triple);
237        assert!(result.is_ok(), "Input+Output+Error scenario should be Ok");
238        let steps = result.unwrap().steps().to_vec();
239        // Expect: [EnsureInputRequestIdsMatchOutputRequestIdsCombinedWithErrorRequestIds, ProcessBatchErrorFile, ProcessBatchOutputFile, MoveBatchTripleToTheDoneDirectory]
240        pretty_assert_eq!(
241            steps,
242            vec![
243                BatchFileTripleReconciliationOperation::EnsureInputRequestIdsMatchOutputRequestIdsCombinedWithErrorRequestIds,
244                BatchFileTripleReconciliationOperation::ProcessBatchErrorFile,
245                BatchFileTripleReconciliationOperation::ProcessBatchOutputFile,
246                BatchFileTripleReconciliationOperation::MoveBatchTripleToTheDoneDirectory
247            ]
248        );
249    }
250}