recovery.rs (3320B)
1 //! Explicit signer replay and uncertain remote-effect recovery contracts. 2 3 /// What exact replay behavior a signer guarantees. 4 #[non_exhaustive] 5 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 6 #[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] 7 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 8 pub enum ReplayCapability { 9 ExactReplayByRequestId, 10 LocalReplaySafe, 11 NonReplayable, 12 } 13 14 /// Whether a failed call may already have created a durable remote effect. 15 #[non_exhaustive] 16 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] 17 pub enum RemoteEffect { 18 #[default] 19 None, 20 MayHaveOccurred, 21 } 22 23 /// Required durable recovery treatment after a signing failure. 24 #[non_exhaustive] 25 #[derive(Clone, Copy, Debug, PartialEq, Eq)] 26 pub enum RecoveryDisposition { 27 RetryExactRequest, 28 RetryLocal, 29 Indeterminate, 30 Failed, 31 } 32 33 #[must_use] 34 pub const fn recovery_disposition( 35 replay: ReplayCapability, 36 remote_effect: RemoteEffect, 37 retryable: bool, 38 ) -> RecoveryDisposition { 39 if !retryable { 40 return RecoveryDisposition::Failed; 41 } 42 match (replay, remote_effect) { 43 (ReplayCapability::ExactReplayByRequestId, _) => RecoveryDisposition::RetryExactRequest, 44 (ReplayCapability::LocalReplaySafe, RemoteEffect::None) => RecoveryDisposition::RetryLocal, 45 (ReplayCapability::NonReplayable, RemoteEffect::MayHaveOccurred) 46 | (ReplayCapability::LocalReplaySafe, RemoteEffect::MayHaveOccurred) => { 47 RecoveryDisposition::Indeterminate 48 } 49 (ReplayCapability::NonReplayable, RemoteEffect::None) => RecoveryDisposition::Failed, 50 } 51 } 52 53 #[cfg(test)] 54 mod tests { 55 use super::*; 56 57 #[test] 58 fn recovery_decision_table_is_exhaustive() { 59 for replay in [ 60 ReplayCapability::ExactReplayByRequestId, 61 ReplayCapability::LocalReplaySafe, 62 ReplayCapability::NonReplayable, 63 ] { 64 for remote_effect in [RemoteEffect::None, RemoteEffect::MayHaveOccurred] { 65 assert_eq!( 66 recovery_disposition(replay, remote_effect, false), 67 RecoveryDisposition::Failed 68 ); 69 } 70 } 71 assert_eq!( 72 recovery_disposition( 73 ReplayCapability::ExactReplayByRequestId, 74 RemoteEffect::MayHaveOccurred, 75 true, 76 ), 77 RecoveryDisposition::RetryExactRequest 78 ); 79 assert_eq!( 80 recovery_disposition(ReplayCapability::LocalReplaySafe, RemoteEffect::None, true,), 81 RecoveryDisposition::RetryLocal 82 ); 83 assert_eq!( 84 recovery_disposition( 85 ReplayCapability::LocalReplaySafe, 86 RemoteEffect::MayHaveOccurred, 87 true, 88 ), 89 RecoveryDisposition::Indeterminate 90 ); 91 assert_eq!( 92 recovery_disposition( 93 ReplayCapability::NonReplayable, 94 RemoteEffect::MayHaveOccurred, 95 true, 96 ), 97 RecoveryDisposition::Indeterminate 98 ); 99 assert_eq!( 100 recovery_disposition(ReplayCapability::NonReplayable, RemoteEffect::None, true,), 101 RecoveryDisposition::Failed 102 ); 103 } 104 }