decode.rs (8962B)
1 //! Deterministic event decoding boundary. 2 //! 3 //! Decoding validates the wire structure and returns a [`RawEvent`]. It does 4 //! not verify the event identifier, signature, or contract. Those transitions 5 //! remain explicit under [`crate::verify`]. 6 7 use core::fmt; 8 use radroots_event::{envelope::EventEnvelopeError, wire::EventWireError}; 9 10 // Domain-specific parsers live behind the canonical decoding namespace. 11 pub use crate::error::EventParseError; 12 pub use crate::parsed::{RadrootsParsedData, RadrootsParsedEvent}; 13 14 pub mod event_ref { 15 pub use crate::event_ref::*; 16 } 17 18 pub mod parsed { 19 pub use crate::parsed::*; 20 } 21 22 pub mod wire { 23 pub use crate::wire::*; 24 } 25 26 macro_rules! decode_domain { 27 ($name:ident, $source:path) => { 28 pub mod $name { 29 pub use $source::*; 30 } 31 }; 32 } 33 34 decode_domain!(app_data, crate::app_data::decode); 35 decode_domain!(article, crate::article::decode); 36 decode_domain!(calendar, crate::calendar::decode); 37 #[cfg(feature = "json")] 38 decode_domain!(coop, crate::coop::decode); 39 #[cfg(feature = "json")] 40 decode_domain!(document, crate::document::decode); 41 #[cfg(feature = "json")] 42 decode_domain!(farm_crdt, crate::farm_crdt::decode); 43 decode_domain!(farm_file, crate::farm_file::decode); 44 #[cfg(feature = "json")] 45 decode_domain!(farm_workspace, crate::farm_workspace::decode); 46 decode_domain!(file_metadata, crate::file_metadata::decode); 47 decode_domain!(follow, crate::follow::decode); 48 decode_domain!(geochat, crate::geochat::decode); 49 decode_domain!(gift_wrap, crate::gift_wrap::decode); 50 decode_domain!(group, crate::group::decode); 51 decode_domain!(http_auth, crate::http_auth::decode); 52 decode_domain!(list, crate::list::decode); 53 decode_domain!(list_set, crate::list_set::decode); 54 decode_domain!(message, crate::message::decode); 55 decode_domain!(message_file, crate::message_file::decode); 56 #[cfg(feature = "json")] 57 decode_domain!(plot, crate::plot::decode); 58 decode_domain!(reaction, crate::reaction::decode); 59 decode_domain!(relay_auth, crate::relay_auth::decode); 60 #[cfg(feature = "json")] 61 decode_domain!(relay_document, crate::relay_document::decode); 62 decode_domain!(report, crate::report::decode); 63 decode_domain!(repost, crate::repost::decode); 64 #[cfg(feature = "json")] 65 decode_domain!(resource_area, crate::resource_area::decode); 66 #[cfg(feature = "json")] 67 decode_domain!(resource_cap, crate::resource_cap::decode); 68 decode_domain!(seal, crate::seal::decode); 69 70 pub mod comment { 71 pub use crate::comment::inbound::*; 72 } 73 74 pub mod deletion { 75 pub use crate::deletion::inbound::*; 76 pub use crate::deletion::reconciliation_v1::inbound as reconciliation_v1; 77 } 78 79 #[cfg(feature = "json")] 80 pub mod farm { 81 pub use crate::farm::decode::*; 82 } 83 84 pub mod food_availability { 85 pub use crate::food_availability::inbound::*; 86 } 87 88 pub mod job { 89 pub use crate::job::error::*; 90 pub use crate::job::traits::*; 91 pub use crate::job::util::*; 92 93 pub mod feedback { 94 pub use crate::job::feedback::decode::*; 95 } 96 pub mod request { 97 pub use crate::job::request::decode::*; 98 } 99 pub mod result { 100 pub use crate::job::result::decode::*; 101 } 102 } 103 104 #[cfg(feature = "knowledge")] 105 pub mod knowledge { 106 pub use crate::knowledge::decode::*; 107 } 108 109 pub mod operational_listing { 110 pub use crate::operational_listing::decode::*; 111 } 112 113 #[cfg(feature = "json")] 114 pub mod order { 115 pub use crate::order::decode::*; 116 } 117 118 pub mod post { 119 pub use crate::post::inbound::*; 120 } 121 122 #[cfg(feature = "json")] 123 pub mod profile { 124 pub use crate::profile::inbound::*; 125 } 126 127 pub mod reply { 128 pub use crate::reply::inbound::*; 129 } 130 131 #[cfg(feature = "json")] 132 pub mod rhi { 133 pub use crate::rhi::{ 134 RadrootsRhiEvidenceAttestationError, RadrootsRhiEvidenceAttestationOutcomeV1, 135 RadrootsRhiEvidenceAttestationSupersessionV1, RadrootsRhiEvidenceAttestationV1, 136 rhi_evidence_attestation_from_event, rhi_evidence_attestation_from_verified_event, 137 validate_rhi_evidence_attestation_supersession, validate_rhi_evidence_attestation_tags, 138 }; 139 } 140 141 #[cfg(feature = "json")] 142 pub mod trade { 143 pub use crate::trade::{ 144 RadrootsTradeMutationError, trade_mutation_from_event, trade_mutation_from_verified_event, 145 validate_trade_mutation_tags, 146 }; 147 } 148 149 #[cfg(feature = "json")] 150 use radroots_event::{ 151 SignedEvent, 152 admission::RawEvent, 153 wire::{DEFAULT_RAW_JSON_MAX_BYTES, Nip01EventWire}, 154 }; 155 156 /// Maximum compact NIP-01 event JSON size accepted by [`event`]. 157 #[cfg(feature = "json")] 158 pub const MAX_EVENT_JSON_BYTES: usize = DEFAULT_RAW_JSON_MAX_BYTES; 159 160 /// A failure while decoding an untrusted event representation. 161 #[non_exhaustive] 162 #[derive(Clone, Debug, PartialEq, Eq)] 163 pub enum DecodeError { 164 /// The encoded event exceeds the public input budget. 165 InputTooLarge { max: usize, actual: usize }, 166 /// The input is not a structurally valid event JSON object. 167 InvalidJson, 168 /// A decoded field violates the native event-envelope contract. 169 InvalidEnvelope(EventEnvelopeError), 170 /// A decoded field violates the bounded NIP-01 wire contract. 171 InvalidWire(EventWireError), 172 } 173 174 impl DecodeError { 175 /// Returns a stable machine-readable error code. 176 #[must_use] 177 pub const fn code(&self) -> &'static str { 178 match self { 179 Self::InputTooLarge { .. } => "input_too_large", 180 Self::InvalidJson => "invalid_json", 181 Self::InvalidEnvelope(_) => "invalid_envelope", 182 Self::InvalidWire(_) => "invalid_wire", 183 } 184 } 185 } 186 187 impl fmt::Display for DecodeError { 188 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { 189 match self { 190 Self::InputTooLarge { max, actual } => { 191 write!(formatter, "event JSON size {actual} exceeds {max} bytes") 192 } 193 Self::InvalidJson => formatter.write_str("event JSON is invalid"), 194 Self::InvalidEnvelope(error) => write!(formatter, "event envelope is invalid: {error}"), 195 Self::InvalidWire(error) => write!(formatter, "event wire is invalid: {error}"), 196 } 197 } 198 } 199 200 #[cfg(feature = "std")] 201 impl std::error::Error for DecodeError { 202 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 203 match self { 204 Self::InvalidEnvelope(error) => Some(error), 205 Self::InvalidWire(error) => Some(error), 206 Self::InputTooLarge { .. } | Self::InvalidJson => None, 207 } 208 } 209 } 210 211 /// Decodes compact NIP-01 event JSON without hiding later validation stages. 212 /// 213 /// The returned [`RawEvent`] must still pass [`crate::verify::id`], 214 /// [`crate::verify::signature`], and [`crate::verify::contract`] before it is 215 /// treated as verified or contract-valid. 216 /// 217 /// ```no_run 218 /// # #[cfg(feature = "json")] 219 /// # { 220 /// let raw_json = r#"{"id":"00","pubkey":"00","created_at":0,"kind":1,"tags":[],"content":"","sig":"00"}"#; 221 /// let raw = radroots_event_codec::decode::event(raw_json)?; 222 /// let id_verified = radroots_event_codec::verify::id(raw)?; 223 /// # let _ = id_verified; 224 /// # } 225 /// # Ok::<(), Box<dyn std::error::Error>>(()) 226 /// ``` 227 #[cfg(feature = "json")] 228 pub fn event(raw_json: &str) -> Result<RawEvent, DecodeError> { 229 let max = MAX_EVENT_JSON_BYTES; 230 let actual = raw_json.len(); 231 if actual > max { 232 return Err(DecodeError::InputTooLarge { max, actual }); 233 } 234 235 let wire = Nip01EventWire::parse_json_unverified(raw_json).map_err(map_wire_error)?; 236 let envelope = wire 237 .into_unverified_envelope() 238 .map_err(DecodeError::InvalidEnvelope)?; 239 Ok(RawEvent::new(envelope)) 240 } 241 242 /// Decodes compact NIP-01 JSON into an ID-verified signed event. 243 #[cfg(feature = "json")] 244 pub fn signed_event(raw_json: &str) -> Result<SignedEvent, DecodeError> { 245 let max = MAX_EVENT_JSON_BYTES; 246 let actual = raw_json.len(); 247 if actual > max { 248 return Err(DecodeError::InputTooLarge { max, actual }); 249 } 250 let wire = Nip01EventWire::parse_json(raw_json).map_err(map_wire_error)?; 251 SignedEvent::from_wire_verified_id(wire, raw_json).map_err(|error| match error { 252 radroots_event::draft::SignedEventError::Envelope(error) => { 253 DecodeError::InvalidEnvelope(error) 254 } 255 radroots_event::draft::SignedEventError::Wire(error) 256 | radroots_event::draft::SignedEventError::RawJson(error) => map_wire_error(error), 257 radroots_event::draft::SignedEventError::RawJsonMismatch => DecodeError::InvalidJson, 258 }) 259 } 260 261 #[cfg(feature = "json")] 262 fn map_wire_error(error: EventWireError) -> DecodeError { 263 match error { 264 EventWireError::RawJsonTooLarge { max, actual } => { 265 DecodeError::InputTooLarge { max, actual } 266 } 267 EventWireError::Json(_) 268 | EventWireError::RootNotObject 269 | EventWireError::MissingField(_) 270 | EventWireError::InvalidField(_) => DecodeError::InvalidJson, 271 EventWireError::Envelope(error) => DecodeError::InvalidEnvelope(error), 272 error => DecodeError::InvalidWire(error), 273 } 274 }