admission.rs (5390B)
1 use core::fmt; 2 3 use radroots_event::{contract::EventContract, envelope::EventEnvelope}; 4 5 use crate::{ 6 post::inbound::{ 7 RadrootsInboundPostProjection, RadrootsPostProjectionError, project_verified_post_event, 8 }, 9 verification::{ 10 RadrootsNip01VerificationError, RadrootsSignatureVerifiedEvent, verify_nip01_event, 11 }, 12 }; 13 14 /// A signature-and-id verified root kind-1 event bound to a product projection. 15 #[derive(Clone, Debug, PartialEq, Eq)] 16 pub struct RadrootsAdmittedRootPostEvent { 17 verified_event: RadrootsSignatureVerifiedEvent, 18 projection: RadrootsInboundPostProjection, 19 } 20 21 impl RadrootsAdmittedRootPostEvent { 22 pub fn verified_event(&self) -> &RadrootsSignatureVerifiedEvent { 23 &self.verified_event 24 } 25 26 pub fn event(&self) -> &EventEnvelope { 27 self.verified_event.event() 28 } 29 30 pub fn projection(&self) -> &RadrootsInboundPostProjection { 31 &self.projection 32 } 33 34 pub fn contract(&self) -> &'static EventContract { 35 debug_assert!(self.projection.classification().is_root_card()); 36 radroots_event::contract::event_contract(self.projection.classification().contract_id()) 37 .expect("root post projection contract IDs are registry-owned") 38 } 39 40 pub fn into_parts( 41 self, 42 ) -> ( 43 RadrootsSignatureVerifiedEvent, 44 RadrootsInboundPostProjection, 45 ) { 46 (self.verified_event, self.projection) 47 } 48 } 49 50 /// A verified kind-1 event excluded from root-card admission by an `e` tag. 51 /// 52 /// This candidate carries no Reply claim. Promotion into a semantic thread 53 /// model is available only through the dedicated NIP-10 admission boundary. 54 #[derive(Clone, Debug, PartialEq, Eq)] 55 pub struct RadrootsThreadExcludedPostCandidate { 56 verified_event: RadrootsSignatureVerifiedEvent, 57 projection: RadrootsInboundPostProjection, 58 } 59 60 impl RadrootsThreadExcludedPostCandidate { 61 pub fn verified_event(&self) -> &RadrootsSignatureVerifiedEvent { 62 &self.verified_event 63 } 64 65 pub fn event(&self) -> &EventEnvelope { 66 self.verified_event.event() 67 } 68 69 pub fn projection(&self) -> &RadrootsInboundPostProjection { 70 &self.projection 71 } 72 73 pub fn into_parts( 74 self, 75 ) -> ( 76 RadrootsSignatureVerifiedEvent, 77 RadrootsInboundPostProjection, 78 ) { 79 (self.verified_event, self.projection) 80 } 81 } 82 83 /// Result of verifying a kind-1 event at the root-post admission boundary. 84 #[non_exhaustive] 85 #[derive(Clone, Debug, PartialEq, Eq)] 86 pub enum RadrootsPostAdmissionOutcome { 87 Root(RadrootsAdmittedRootPostEvent), 88 ThreadExcluded(RadrootsThreadExcludedPostCandidate), 89 } 90 91 #[non_exhaustive] 92 #[derive(Clone, Debug, PartialEq, Eq)] 93 pub enum RadrootsPostAdmissionError { 94 Nip01Verification(RadrootsNip01VerificationError), 95 Projection(RadrootsPostProjectionError), 96 } 97 98 impl RadrootsPostAdmissionError { 99 pub const fn code(&self) -> &'static str { 100 match self { 101 Self::Nip01Verification(error) => error.code(), 102 Self::Projection(error) => error.code(), 103 } 104 } 105 } 106 107 impl fmt::Display for RadrootsPostAdmissionError { 108 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 109 match self { 110 Self::Nip01Verification(error) => write!(formatter, "{error}"), 111 Self::Projection(error) => write!(formatter, "{error}"), 112 } 113 } 114 } 115 116 #[cfg(feature = "std")] 117 impl std::error::Error for RadrootsPostAdmissionError { 118 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 119 match self { 120 Self::Nip01Verification(error) => Some(error), 121 Self::Projection(error) => Some(error), 122 } 123 } 124 } 125 126 impl From<RadrootsNip01VerificationError> for RadrootsPostAdmissionError { 127 fn from(value: RadrootsNip01VerificationError) -> Self { 128 Self::Nip01Verification(value) 129 } 130 } 131 132 impl From<RadrootsPostProjectionError> for RadrootsPostAdmissionError { 133 fn from(value: RadrootsPostProjectionError) -> Self { 134 Self::Projection(value) 135 } 136 } 137 138 /// Classifies an already verified kind-1 event at the product post boundary. 139 /// 140 /// Root post profiles are admitted as product posts. Thread-shaped candidates 141 /// are returned separately and do not establish a root post or reply contract. 142 pub fn admit_verified_post_event( 143 verified_event: RadrootsSignatureVerifiedEvent, 144 ) -> Result<RadrootsPostAdmissionOutcome, RadrootsPostAdmissionError> { 145 let projection = project_verified_post_event(&verified_event)?; 146 if projection.classification().is_root_card() { 147 Ok(RadrootsPostAdmissionOutcome::Root( 148 RadrootsAdmittedRootPostEvent { 149 verified_event, 150 projection, 151 }, 152 )) 153 } else { 154 Ok(RadrootsPostAdmissionOutcome::ThreadExcluded( 155 RadrootsThreadExcludedPostCandidate { 156 verified_event, 157 projection, 158 }, 159 )) 160 } 161 } 162 163 /// Verifies NIP-01 state, admitting only root profiles as product posts. 164 /// 165 /// A verified thread candidate is returned as thread-excluded compatibility 166 /// data; this boundary does not claim that it is a valid Radroots reply. 167 pub fn verify_and_admit_post_event( 168 event: EventEnvelope, 169 ) -> Result<RadrootsPostAdmissionOutcome, RadrootsPostAdmissionError> { 170 admit_verified_post_event(verify_nip01_event(event)?) 171 }