lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }