Skip to main content

sabi/
txn_failure.rs

1// Copyright (C) 2026 Takayuki Sato. All Rights Reserved.
2// This program is free software under MIT License.
3// See the file LICENSE in this distribution for more details.
4
5use crate::{TxnFailureCause, TxnFailureRecovery, TxnFailureReport, TxnFailureRollback};
6
7use std::sync::Arc;
8
9impl TxnFailureReport {
10    pub(crate) fn new(name: Arc<str>, typ: &'static str) -> Self {
11        Self {
12            data_conn_name: name,
13            data_conn_type: typ,
14            cause: TxnFailureCause::NoneByUncommitted,
15            rollback: TxnFailureRollback::NoneByNotRolledBack,
16        }
17    }
18
19    /// Returns `true` if this connection was a cause of the transaction failure.
20    pub fn is_cause_of_failure(&self) -> bool {
21        !matches!(
22            self.cause,
23            TxnFailureCause::NoneByCommitted | TxnFailureCause::NoneByUncommitted
24        )
25    }
26
27    /// Determines the suggested recovery action for this connection to achieve a successful commit.
28    pub fn recovery_for_commit(&self) -> TxnFailureRecovery {
29        use TxnFailureCause::*;
30        use TxnFailureRecovery::*;
31        use TxnFailureRollback::*;
32
33        match (&self.cause, &self.rollback) {
34            (NoneByUncommitted, NoneByNotRolledBack) => InvestigateBecauseImpossible,
35            (NoneByUncommitted, NoneByRolledBack) => RerunLogicAndCommit,
36            (NoneByUncommitted, RollbackFailure(_)) => ResolveCauseAndInconsistency,
37
38            (NoneByCommitted, NoneByNotRolledBack) => NoActionRequired,
39            (NoneByCommitted, NoneByRolledBack) => InvestigateBecauseImpossible,
40            (NoneByCommitted, RollbackFailure(_)) => InvestigateBecauseImpossible,
41
42            (LogicFailure(_), NoneByNotRolledBack) => InvestigateBecauseImpossible,
43            (LogicFailure(_), NoneByRolledBack) => ResolveCauseThenRerunLogicAndCommit,
44            (LogicFailure(_), RollbackFailure(_)) => ResolveCauseAndInconsistency,
45
46            (CommitFailure(_), NoneByNotRolledBack) => InvestigateBecauseImpossible,
47            (CommitFailure(_), NoneByRolledBack) => ResolveCauseThenRerunLogicAndCommit,
48            (CommitFailure(_), RollbackFailure(_)) => ResolveCauseAndInconsistency,
49
50            (PostCommitFailure(_), NoneByNotRolledBack) => ResolveCauseThenRerunPostCommit,
51            (PostCommitFailure(_), NoneByRolledBack) => InvestigateBecauseImpossible,
52            (PostCommitFailure(_), RollbackFailure(_)) => InvestigateBecauseImpossible,
53        }
54    }
55
56    /// Determines the suggested recovery action to achieve a rolled-back state for this connection.
57    pub fn recovery_for_rollback(&self) -> TxnFailureRecovery {
58        use TxnFailureCause::*;
59        use TxnFailureRecovery::*;
60        use TxnFailureRollback::*;
61
62        match (&self.cause, &self.rollback) {
63            (NoneByUncommitted, NoneByNotRolledBack) => InvestigateBecauseImpossible,
64            (NoneByUncommitted, NoneByRolledBack) => NoActionRequired,
65            (NoneByUncommitted, RollbackFailure(_)) => ResolveCauseAndInconsistency,
66
67            (NoneByCommitted, NoneByNotRolledBack) => ManualRollbackRequired,
68            (NoneByCommitted, NoneByRolledBack) => InvestigateBecauseImpossible,
69            (NoneByCommitted, RollbackFailure(_)) => InvestigateBecauseImpossible,
70
71            (LogicFailure(_), NoneByNotRolledBack) => InvestigateBecauseImpossible,
72            (LogicFailure(_), NoneByRolledBack) => NoActionRequired,
73            (LogicFailure(_), RollbackFailure(_)) => ResolveCauseAndInconsistency,
74
75            (CommitFailure(_), NoneByNotRolledBack) => InvestigateBecauseImpossible,
76            (CommitFailure(_), NoneByRolledBack) => NoActionRequired,
77            (CommitFailure(_), RollbackFailure(_)) => ResolveCauseAndInconsistency,
78
79            (PostCommitFailure(_), NoneByNotRolledBack) => ManualRollbackRequired,
80            (PostCommitFailure(_), NoneByRolledBack) => InvestigateBecauseImpossible,
81            (PostCommitFailure(_), RollbackFailure(_)) => InvestigateBecauseImpossible,
82        }
83    }
84}
85
86#[cfg_attr(coverage_nightly, coverage(off))]
87#[cfg(test)]
88mod unit_tests {
89    use super::*;
90    use TxnFailureCause::*;
91    use TxnFailureRecovery::*;
92    use TxnFailureRollback::*;
93
94    // impossible case
95    #[test]
96    fn test_cause_is_none_by_uncommitted_and_rollback_is_none_by_not_rolled_back() {
97        let report = TxnFailureReport {
98            data_conn_name: "foo".into(),
99            data_conn_type: "A::B::C",
100            cause: NoneByUncommitted,
101            rollback: NoneByNotRolledBack,
102        };
103
104        assert!(!report.is_cause_of_failure());
105        assert_eq!(report.recovery_for_commit(), InvestigateBecauseImpossible);
106        assert_eq!(report.recovery_for_rollback(), InvestigateBecauseImpossible);
107    }
108
109    #[test]
110    fn test_cause_is_none_by_uncommitted_and_rollback_is_none_by_rolled_back() {
111        let report = TxnFailureReport {
112            data_conn_name: "foo".into(),
113            data_conn_type: "A::B::C",
114            cause: NoneByUncommitted,
115            rollback: NoneByRolledBack,
116        };
117
118        assert!(!report.is_cause_of_failure());
119        assert_eq!(report.recovery_for_commit(), RerunLogicAndCommit);
120        assert_eq!(report.recovery_for_rollback(), NoActionRequired);
121    }
122
123    #[test]
124    fn test_cause_is_none_by_uncommitted_and_rollback_is_rollback_failure() {
125        let report = TxnFailureReport {
126            data_conn_name: "foo".into(),
127            data_conn_type: "A::B::C",
128            cause: NoneByUncommitted,
129            rollback: RollbackFailure(errs::Err::new("fail")),
130        };
131
132        assert!(!report.is_cause_of_failure());
133        assert_eq!(report.recovery_for_commit(), ResolveCauseAndInconsistency);
134        assert_eq!(report.recovery_for_rollback(), ResolveCauseAndInconsistency);
135    }
136
137    #[test]
138    fn test_cause_is_none_by_committed_and_rollback_is_none_by_not_rolled_back() {
139        let report = TxnFailureReport {
140            data_conn_name: "foo".into(),
141            data_conn_type: "A::B::C",
142            cause: NoneByCommitted,
143            rollback: NoneByNotRolledBack,
144        };
145
146        assert!(!report.is_cause_of_failure());
147        assert_eq!(report.recovery_for_commit(), NoActionRequired);
148        assert_eq!(report.recovery_for_rollback(), ManualRollbackRequired);
149    }
150
151    // impossible case
152    #[test]
153    fn test_cause_is_none_by_committed_and_rollback_is_none_by_rolled_back() {
154        let report = TxnFailureReport {
155            data_conn_name: "foo".into(),
156            data_conn_type: "A::B::C",
157            cause: TxnFailureCause::NoneByCommitted,
158            rollback: TxnFailureRollback::NoneByRolledBack,
159        };
160
161        assert!(!report.is_cause_of_failure());
162        assert_eq!(report.recovery_for_commit(), InvestigateBecauseImpossible);
163        assert_eq!(report.recovery_for_rollback(), InvestigateBecauseImpossible);
164    }
165
166    // impossible case
167    #[test]
168    fn test_cause_is_none_by_committed_and_rollback_is_rollback_failure() {
169        let report = TxnFailureReport {
170            data_conn_name: "foo".into(),
171            data_conn_type: "A::B::C",
172            cause: TxnFailureCause::NoneByCommitted,
173            rollback: TxnFailureRollback::RollbackFailure(errs::Err::new("fail")),
174        };
175
176        assert!(!report.is_cause_of_failure());
177        assert_eq!(report.recovery_for_commit(), InvestigateBecauseImpossible);
178        assert_eq!(report.recovery_for_rollback(), InvestigateBecauseImpossible);
179    }
180
181    #[test]
182    fn test_cause_is_logic_failure_and_rollback_is_none_by_not_rolled_back() {
183        let report = TxnFailureReport {
184            data_conn_name: "foo".into(),
185            data_conn_type: "A::B::C",
186            cause: LogicFailure(errs::Err::new("fail")),
187            rollback: NoneByNotRolledBack,
188        };
189
190        assert!(report.is_cause_of_failure());
191        assert_eq!(report.recovery_for_commit(), InvestigateBecauseImpossible);
192        assert_eq!(report.recovery_for_rollback(), InvestigateBecauseImpossible);
193    }
194
195    #[test]
196    fn test_cause_is_run_failure_and_rollback_is_none_by_rolled_back() {
197        let report = TxnFailureReport {
198            data_conn_name: "foo".into(),
199            data_conn_type: "A::B::C",
200            cause: LogicFailure(errs::Err::new("fail")),
201            rollback: NoneByRolledBack,
202        };
203
204        assert!(report.is_cause_of_failure());
205        assert_eq!(
206            report.recovery_for_commit(),
207            ResolveCauseThenRerunLogicAndCommit
208        );
209        assert_eq!(report.recovery_for_rollback(), NoActionRequired);
210    }
211
212    #[test]
213    fn test_cause_is_run_failure_and_rollback_is_rollback_failure() {
214        let report = TxnFailureReport {
215            data_conn_name: "foo".into(),
216            data_conn_type: "A::B::C",
217            cause: LogicFailure(errs::Err::new("fail")),
218            rollback: RollbackFailure(errs::Err::new("fail")),
219        };
220
221        assert!(report.is_cause_of_failure());
222        assert_eq!(report.recovery_for_commit(), ResolveCauseAndInconsistency);
223        assert_eq!(report.recovery_for_rollback(), ResolveCauseAndInconsistency);
224    }
225
226    #[test]
227    fn test_cause_is_commit_failure_and_rollback_is_none_by_not_rolled_back() {
228        let report = TxnFailureReport {
229            data_conn_name: "foo".into(),
230            data_conn_type: "A::B::C",
231            cause: CommitFailure(errs::Err::new("fail")),
232            rollback: NoneByNotRolledBack,
233        };
234
235        assert!(report.is_cause_of_failure());
236        assert_eq!(report.recovery_for_commit(), InvestigateBecauseImpossible);
237        assert_eq!(report.recovery_for_rollback(), InvestigateBecauseImpossible);
238    }
239
240    #[test]
241    fn test_cause_is_commit_failure_and_rollback_is_none_by_rolled_back() {
242        let report = TxnFailureReport {
243            data_conn_name: "foo".into(),
244            data_conn_type: "A::B::C",
245            cause: CommitFailure(errs::Err::new("fail")),
246            rollback: NoneByRolledBack,
247        };
248
249        assert!(report.is_cause_of_failure());
250        assert_eq!(
251            report.recovery_for_commit(),
252            ResolveCauseThenRerunLogicAndCommit
253        );
254        assert_eq!(report.recovery_for_rollback(), NoActionRequired);
255    }
256
257    #[test]
258    fn test_cause_is_commit_failure_and_rollback_is_rollback_failure() {
259        let report = TxnFailureReport {
260            data_conn_name: "foo".into(),
261            data_conn_type: "A::B::C",
262            cause: TxnFailureCause::CommitFailure(errs::Err::new("fail")),
263            rollback: TxnFailureRollback::RollbackFailure(errs::Err::new("fail")),
264        };
265
266        assert!(report.is_cause_of_failure());
267        assert_eq!(report.recovery_for_commit(), ResolveCauseAndInconsistency);
268        assert_eq!(report.recovery_for_rollback(), ResolveCauseAndInconsistency);
269    }
270
271    #[test]
272    fn test_cause_is_post_commit_failure_and_rollback_is_none_by_not_rolled_back() {
273        let report = TxnFailureReport {
274            data_conn_name: "foo".into(),
275            data_conn_type: "A::B::C",
276            cause: PostCommitFailure(errs::Err::new("fail")),
277            rollback: NoneByNotRolledBack,
278        };
279
280        assert!(report.is_cause_of_failure());
281        assert_eq!(
282            report.recovery_for_commit(),
283            ResolveCauseThenRerunPostCommit
284        );
285        assert_eq!(report.recovery_for_rollback(), ManualRollbackRequired);
286    }
287
288    #[test]
289    fn test_cause_is_post_commit_failure_and_rollback_is_none_by_rolled_back() {
290        let report = TxnFailureReport {
291            data_conn_name: "foo".into(),
292            data_conn_type: "A::B::C",
293            cause: PostCommitFailure(errs::Err::new("fail")),
294            rollback: NoneByRolledBack,
295        };
296
297        assert!(report.is_cause_of_failure());
298        assert_eq!(report.recovery_for_commit(), InvestigateBecauseImpossible);
299        assert_eq!(report.recovery_for_rollback(), InvestigateBecauseImpossible);
300    }
301
302    #[test]
303    fn test_cause_is_post_commit_failure_and_rollback_is_rollback_failure() {
304        let report = TxnFailureReport {
305            data_conn_name: "foo".into(),
306            data_conn_type: "A::B::C",
307            cause: TxnFailureCause::PostCommitFailure(errs::Err::new("fail")),
308            rollback: TxnFailureRollback::RollbackFailure(errs::Err::new("fail")),
309        };
310
311        assert!(report.is_cause_of_failure());
312        assert_eq!(report.recovery_for_commit(), InvestigateBecauseImpossible);
313        assert_eq!(report.recovery_for_rollback(), InvestigateBecauseImpossible);
314    }
315}