revision.rs (3883B)
1 use core::fmt; 2 3 use crate::{ 4 food_availability::inbound::{ 5 RadrootsFoodAvailabilityProjectionError, project_strict_verified_food_availability_event, 6 }, 7 verification::RadrootsSignatureVerifiedEvent, 8 }; 9 10 #[non_exhaustive] 11 #[derive(Clone, Debug, PartialEq, Eq)] 12 pub enum RadrootsFoodAvailabilityRevisionError { 13 PreviousInvalid(RadrootsFoodAvailabilityProjectionError), 14 CurrentInvalid(RadrootsFoodAvailabilityProjectionError), 15 CoordinateChanged, 16 PublishedAtChanged, 17 NotNewer, 18 } 19 20 impl RadrootsFoodAvailabilityRevisionError { 21 pub const fn code(&self) -> &'static str { 22 match self { 23 Self::PreviousInvalid(_) => "food_revision_previous_invalid", 24 Self::CurrentInvalid(_) => "food_revision_current_invalid", 25 Self::CoordinateChanged => "food_coordinate_changed", 26 Self::PublishedAtChanged => "food_published_at_changed", 27 Self::NotNewer => "food_revision_not_newer", 28 } 29 } 30 } 31 32 impl fmt::Display for RadrootsFoodAvailabilityRevisionError { 33 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 34 match self { 35 Self::PreviousInvalid(error) => { 36 write!( 37 formatter, 38 "previous FoodAvailability revision is invalid: {error}" 39 ) 40 } 41 Self::CurrentInvalid(error) => { 42 write!( 43 formatter, 44 "current FoodAvailability revision is invalid: {error}" 45 ) 46 } 47 Self::CoordinateChanged => { 48 formatter.write_str("FoodAvailability revision coordinate changed") 49 } 50 Self::PublishedAtChanged => { 51 formatter.write_str("FoodAvailability revision published_at changed") 52 } 53 Self::NotNewer => { 54 formatter.write_str("FoodAvailability revision does not win NIP-01 ordering") 55 } 56 } 57 } 58 } 59 60 #[cfg(feature = "std")] 61 impl std::error::Error for RadrootsFoodAvailabilityRevisionError { 62 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 63 match self { 64 Self::PreviousInvalid(error) | Self::CurrentInvalid(error) => Some(error), 65 _ => None, 66 } 67 } 68 } 69 70 /// Validates a replacement using strict authored wire semantics on both sides. 71 /// 72 /// Both inputs must already have passed NIP-01 id and signature verification. 73 pub fn validate_food_availability_revision( 74 previous: &RadrootsSignatureVerifiedEvent, 75 current: &RadrootsSignatureVerifiedEvent, 76 ) -> Result<(), RadrootsFoodAvailabilityRevisionError> { 77 let previous_projection = project_strict_verified_food_availability_event(previous) 78 .map_err(RadrootsFoodAvailabilityRevisionError::PreviousInvalid)?; 79 let current_projection = project_strict_verified_food_availability_event(current) 80 .map_err(RadrootsFoodAvailabilityRevisionError::CurrentInvalid)?; 81 let previous_event = previous.event(); 82 let current_event = current.event(); 83 84 if previous_event.kind_u32() != current_event.kind_u32() 85 || previous_event.author().to_hex() != current_event.author().to_hex() 86 || previous_projection.identifier() != current_projection.identifier() 87 { 88 return Err(RadrootsFoodAvailabilityRevisionError::CoordinateChanged); 89 } 90 if previous_projection.published_at() != current_projection.published_at() { 91 return Err(RadrootsFoodAvailabilityRevisionError::PublishedAtChanged); 92 } 93 94 let current_wins = current_event.created_at_u64() > previous_event.created_at_u64() 95 || (current_event.created_at_u64() == previous_event.created_at_u64() 96 && current_event.id() < previous_event.id()); 97 if !current_wins { 98 return Err(RadrootsFoodAvailabilityRevisionError::NotNewer); 99 } 100 Ok(()) 101 }