invalidation.rs (6914B)
1 //! Versioned owned notification DTOs; durable records remain query-owned. 2 3 use tera_core::runtime::invalidation::{InvalidationDomain, RuntimeInvalidation}; 4 5 use crate::FfiLocalNetworkRecord; 6 7 pub const RUNTIME_CHANGE_SCHEMA_VERSION: u16 = 3; 8 9 /// A gap invalidates all query domains in the authenticated runtime scope. 10 /// Its revision is not a watermark and must not suppress a final resnapshot. 11 #[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)] 12 pub enum FfiRuntimeChangeDelivery { 13 Change, 14 ResnapshotRequired, 15 } 16 17 #[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)] 18 pub enum FfiRuntimeChangeKind { 19 Initial, 20 Identity, 21 Settings, 22 Profile, 23 Today, 24 Drafts, 25 Relay, 26 Media, 27 Lifecycle, 28 } 29 30 #[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)] 31 pub struct FfiRuntimeChangeScope { 32 pub public_key: String, 33 pub source_generation: String, 34 pub context: Option<FfiLocalNetworkRecord>, 35 } 36 37 #[derive(Clone, Copy, Debug, Eq, PartialEq, uniffi::Enum)] 38 pub enum FfiInvalidationRevision { 39 Current { value: u64 }, 40 Exhausted, 41 } 42 43 #[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)] 44 pub struct FfiRuntimeChangeRecord { 45 pub schema_version: u16, 46 pub scope: FfiRuntimeChangeScope, 47 pub epoch: String, 48 pub revision: FfiInvalidationRevision, 49 pub delivery: FfiRuntimeChangeDelivery, 50 pub kind: FfiRuntimeChangeKind, 51 pub entity_id: Option<String>, 52 } 53 54 impl From<RuntimeInvalidation> for FfiRuntimeChangeRecord { 55 fn from(value: RuntimeInvalidation) -> Self { 56 Self { 57 schema_version: RUNTIME_CHANGE_SCHEMA_VERSION, 58 scope: FfiRuntimeChangeScope { 59 public_key: value.scope.public_key().to_hex(), 60 source_generation: hex::encode(value.scope.source_generation().as_bytes()), 61 context: value.scope.context().cloned().map(Into::into), 62 }, 63 epoch: format!("{:032x}", value.epoch), 64 revision: match value.revision.value() { 65 Some(value) => FfiInvalidationRevision::Current { value }, 66 None => FfiInvalidationRevision::Exhausted, 67 }, 68 delivery: FfiRuntimeChangeDelivery::Change, 69 kind: value.domain.into(), 70 entity_id: value.entity_id, 71 } 72 } 73 } 74 75 impl FfiRuntimeChangeRecord { 76 pub(crate) fn requiring_resnapshot(mut self) -> Self { 77 self.scope.context = None; 78 self.kind = FfiRuntimeChangeKind::Initial; 79 self.revision = FfiInvalidationRevision::Current { value: 0 }; 80 self.delivery = FfiRuntimeChangeDelivery::ResnapshotRequired; 81 self.entity_id = None; 82 self 83 } 84 } 85 86 impl From<FfiRuntimeChangeKind> for InvalidationDomain { 87 fn from(value: FfiRuntimeChangeKind) -> Self { 88 match value { 89 FfiRuntimeChangeKind::Initial => Self::Initial, 90 FfiRuntimeChangeKind::Identity => Self::Identity, 91 FfiRuntimeChangeKind::Settings => Self::Settings, 92 FfiRuntimeChangeKind::Profile => Self::Profile, 93 FfiRuntimeChangeKind::Today => Self::Today, 94 FfiRuntimeChangeKind::Drafts => Self::Drafts, 95 FfiRuntimeChangeKind::Relay => Self::Relay, 96 FfiRuntimeChangeKind::Media => Self::Media, 97 FfiRuntimeChangeKind::Lifecycle => Self::Lifecycle, 98 } 99 } 100 } 101 102 impl From<InvalidationDomain> for FfiRuntimeChangeKind { 103 fn from(value: InvalidationDomain) -> Self { 104 match value { 105 InvalidationDomain::Initial => Self::Initial, 106 InvalidationDomain::Identity => Self::Identity, 107 InvalidationDomain::Settings => Self::Settings, 108 InvalidationDomain::Profile => Self::Profile, 109 InvalidationDomain::Today => Self::Today, 110 InvalidationDomain::Drafts => Self::Drafts, 111 InvalidationDomain::Relay => Self::Relay, 112 InvalidationDomain::Media => Self::Media, 113 InvalidationDomain::Lifecycle => Self::Lifecycle, 114 } 115 } 116 } 117 118 #[cfg(test)] 119 mod tests { 120 use super::*; 121 122 #[test] 123 fn all_domains_and_both_revision_states_round_trip_through_the_generated_codec() { 124 for domain in [ 125 InvalidationDomain::Initial, 126 InvalidationDomain::Identity, 127 InvalidationDomain::Settings, 128 InvalidationDomain::Profile, 129 InvalidationDomain::Today, 130 InvalidationDomain::Drafts, 131 InvalidationDomain::Relay, 132 InvalidationDomain::Media, 133 InvalidationDomain::Lifecycle, 134 ] { 135 let kind = FfiRuntimeChangeKind::from(domain); 136 assert_eq!(InvalidationDomain::from(kind), domain); 137 for revision in [ 138 FfiInvalidationRevision::Current { value: u64::MAX }, 139 FfiInvalidationRevision::Exhausted, 140 ] { 141 let record = FfiRuntimeChangeRecord { 142 schema_version: RUNTIME_CHANGE_SCHEMA_VERSION, 143 scope: FfiRuntimeChangeScope { 144 public_key: "a".repeat(64), 145 source_generation: "b".repeat(64), 146 context: Some(FfiLocalNetworkRecord { 147 schema_version: 1, 148 id: "local".into(), 149 label: "Market".into(), 150 relay_urls: vec!["wss://relay.example".into()], 151 locality: Some("Town".into()), 152 followed_authors: vec!["c".repeat(64)], 153 generation: u64::MAX, 154 }), 155 }, 156 epoch: "d".repeat(32), 157 revision, 158 delivery: FfiRuntimeChangeDelivery::Change, 159 kind, 160 entity_id: Some("draft".into()), 161 }; 162 for record in [record.clone(), record.requiring_resnapshot()] { 163 let mut bytes = Vec::new(); 164 <FfiRuntimeChangeRecord as uniffi::FfiConverter<crate::UniFfiTag>>::write( 165 record.clone(), 166 &mut bytes, 167 ); 168 let mut input = bytes.as_slice(); 169 let decoded = <FfiRuntimeChangeRecord as uniffi::FfiConverter< 170 crate::UniFfiTag, 171 >>::try_read(&mut input) 172 .unwrap(); 173 assert_eq!(decoded, record); 174 assert!(input.is_empty()); 175 let mut truncated = &bytes[..bytes.len() - 1]; 176 assert!( 177 <FfiRuntimeChangeRecord as uniffi::FfiConverter<crate::UniFfiTag>>::try_read( 178 &mut truncated 179 ) 180 .is_err() 181 ); 182 } 183 } 184 } 185 } 186 }