recovery_status.rs (4605B)
1 use crate::{MOBILE_FFI_SCHEMA_VERSION, TeraAppError, TeraRuntime}; 2 use tera_core::runtime::product_surface::recovery_status::{ 3 NativeRecoveryReason, NativeRecoveryStatus, 4 }; 5 6 #[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)] 7 pub enum FfiNativeRecoveryReason { 8 MissingParent, 9 InvalidParent, 10 AssociationMismatch, 11 OutcomeUnconfirmed, 12 Resolved, 13 } 14 15 impl From<FfiNativeRecoveryReason> for NativeRecoveryReason { 16 fn from(value: FfiNativeRecoveryReason) -> Self { 17 match value { 18 FfiNativeRecoveryReason::MissingParent => Self::MissingParent, 19 FfiNativeRecoveryReason::InvalidParent => Self::InvalidParent, 20 FfiNativeRecoveryReason::AssociationMismatch => Self::AssociationMismatch, 21 FfiNativeRecoveryReason::OutcomeUnconfirmed => Self::OutcomeUnconfirmed, 22 FfiNativeRecoveryReason::Resolved => Self::Resolved, 23 } 24 } 25 } 26 27 impl From<NativeRecoveryReason> for FfiNativeRecoveryReason { 28 fn from(value: NativeRecoveryReason) -> Self { 29 match value { 30 NativeRecoveryReason::MissingParent => Self::MissingParent, 31 NativeRecoveryReason::InvalidParent => Self::InvalidParent, 32 NativeRecoveryReason::AssociationMismatch => Self::AssociationMismatch, 33 NativeRecoveryReason::OutcomeUnconfirmed => Self::OutcomeUnconfirmed, 34 NativeRecoveryReason::Resolved => Self::Resolved, 35 } 36 } 37 } 38 39 #[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)] 40 pub struct FfiNativeRecoveryStatus { 41 pub schema_version: u16, 42 pub key: String, 43 pub reason: FfiNativeRecoveryReason, 44 pub revision: u64, 45 pub first_observed_unix_ms: u64, 46 pub updated_at_unix_ms: u64, 47 } 48 49 impl From<NativeRecoveryStatus> for FfiNativeRecoveryStatus { 50 fn from(value: NativeRecoveryStatus) -> Self { 51 Self { 52 schema_version: MOBILE_FFI_SCHEMA_VERSION, 53 key: hex::encode(value.key), 54 reason: value.reason.into(), 55 revision: value.revision, 56 first_observed_unix_ms: value.first_observed_unix_ms, 57 updated_at_unix_ms: value.updated_at_unix_ms, 58 } 59 } 60 } 61 62 fn key(schema_version: u16, value: &str) -> Result<[u8; 32], TeraAppError> { 63 if schema_version != MOBILE_FFI_SCHEMA_VERSION 64 || value.len() != 64 65 || !value 66 .bytes() 67 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) 68 { 69 return Err(TeraAppError::invalid_argument( 70 "invalid_native_recovery_status", 71 )); 72 } 73 let mut result = [0; 32]; 74 hex::decode_to_slice(value, &mut result) 75 .map_err(|_| TeraAppError::invalid_argument("invalid_native_recovery_status"))?; 76 Ok(result) 77 } 78 79 #[cfg_attr(not(coverage_nightly), uniffi::export(async_runtime = "tokio"))] 80 impl TeraRuntime { 81 pub async fn native_recovery_status( 82 &self, 83 schema_version: u16, 84 transfer_key: String, 85 ) -> Result<Option<FfiNativeRecoveryStatus>, TeraAppError> { 86 self.inner 87 .native_recovery_status(key(schema_version, &transfer_key)?) 88 .await 89 .map(|value| value.map(Into::into)) 90 .map_err(Into::into) 91 } 92 93 pub async fn report_native_recovery_status( 94 &self, 95 schema_version: u16, 96 transfer_key: String, 97 reason: FfiNativeRecoveryReason, 98 ) -> Result<Option<FfiNativeRecoveryStatus>, TeraAppError> { 99 self.inner 100 .report_native_recovery_status(key(schema_version, &transfer_key)?, reason.into()) 101 .await 102 .map(|value| value.map(Into::into)) 103 .map_err(Into::into) 104 } 105 } 106 107 #[cfg(test)] 108 mod tests { 109 use super::*; 110 111 #[test] 112 fn status_key_admission_is_versioned_canonical_and_bounded() { 113 assert_eq!(key(1, &"ab".repeat(32)).unwrap(), [0xab; 32]); 114 for value in [ 115 "", 116 &"A".repeat(64), 117 &"0".repeat(63), 118 &"0".repeat(65), 119 &"é".repeat(32), 120 ] { 121 assert!(key(1, value).is_err()); 122 } 123 assert!(key(2, &"0".repeat(64)).is_err()); 124 for reason in [ 125 FfiNativeRecoveryReason::MissingParent, 126 FfiNativeRecoveryReason::InvalidParent, 127 FfiNativeRecoveryReason::AssociationMismatch, 128 FfiNativeRecoveryReason::OutcomeUnconfirmed, 129 FfiNativeRecoveryReason::Resolved, 130 ] { 131 assert_eq!( 132 FfiNativeRecoveryReason::from(NativeRecoveryReason::from(reason)), 133 reason 134 ); 135 } 136 } 137 }