encode.rs (16491B)
1 #[cfg(all(not(feature = "std"), feature = "serde_json"))] 2 use alloc::string::String; 3 4 #[cfg(feature = "serde_json")] 5 use radroots_events::{ 6 RadrootsNostrEventPtr, 7 ids::RadrootsEventId, 8 order::{ 9 RadrootsOrderCancellation, RadrootsOrderDecision, RadrootsOrderEnvelope, 10 RadrootsOrderEnvelopeError, RadrootsOrderEventType, RadrootsOrderPayloadError, 11 RadrootsOrderRequest, RadrootsOrderRevisionDecision, RadrootsOrderRevisionProposal, 12 }, 13 }; 14 15 #[cfg(feature = "serde_json")] 16 use crate::{error::EventEncodeError, order::tags::order_envelope_tags, wire::WireEventParts}; 17 18 #[cfg(feature = "serde_json")] 19 fn map_order_envelope_error(error: RadrootsOrderEnvelopeError) -> EventEncodeError { 20 match error { 21 RadrootsOrderEnvelopeError::MissingOrderId => { 22 EventEncodeError::EmptyRequiredField("order_id") 23 } 24 RadrootsOrderEnvelopeError::MissingListingAddr => { 25 EventEncodeError::EmptyRequiredField("listing_addr") 26 } 27 RadrootsOrderEnvelopeError::InvalidVersion { .. } => { 28 EventEncodeError::InvalidField("version") 29 } 30 } 31 } 32 33 #[cfg(feature = "serde_json")] 34 fn map_order_payload_error(error: RadrootsOrderPayloadError) -> EventEncodeError { 35 match error { 36 RadrootsOrderPayloadError::EmptyField(field) => EventEncodeError::EmptyRequiredField(field), 37 RadrootsOrderPayloadError::MissingItems => EventEncodeError::EmptyRequiredField("items"), 38 RadrootsOrderPayloadError::InvalidItemBinCount { .. } => { 39 EventEncodeError::InvalidField("items.bin_count") 40 } 41 RadrootsOrderPayloadError::MissingEconomicItems => { 42 EventEncodeError::EmptyRequiredField("economics.items") 43 } 44 RadrootsOrderPayloadError::InvalidEconomicItemBinCount { .. } => { 45 EventEncodeError::InvalidField("economics.items.bin_count") 46 } 47 RadrootsOrderPayloadError::InvalidEconomicItemQuantity { .. } => { 48 EventEncodeError::InvalidField("economics.items.quantity_amount") 49 } 50 RadrootsOrderPayloadError::InvalidEconomicItemPrice { .. } => { 51 EventEncodeError::InvalidField("economics.items.unit_price_amount") 52 } 53 RadrootsOrderPayloadError::InvalidEconomicItemSubtotal { .. } => { 54 EventEncodeError::InvalidField("economics.items.line_subtotal") 55 } 56 RadrootsOrderPayloadError::InvalidEconomicLineAmount { field, .. } 57 | RadrootsOrderPayloadError::InvalidEconomicLineKind { field, .. } 58 | RadrootsOrderPayloadError::InvalidEconomicLineEffect { field, .. } 59 | RadrootsOrderPayloadError::InvalidEconomicCurrency { field } 60 | RadrootsOrderPayloadError::InvalidEconomicOrdering { field } 61 | RadrootsOrderPayloadError::InvalidEconomicTotal { field } 62 | RadrootsOrderPayloadError::InvalidOrderEconomicsBinding { field } => { 63 EventEncodeError::InvalidField(field) 64 } 65 RadrootsOrderPayloadError::InvalidQuoteVersion => { 66 EventEncodeError::InvalidField("economics.quote_version") 67 } 68 RadrootsOrderPayloadError::MissingInventoryCommitments => { 69 EventEncodeError::EmptyRequiredField("inventory_commitments") 70 } 71 RadrootsOrderPayloadError::InvalidInventoryCommitmentCount { .. } => { 72 EventEncodeError::InvalidField("inventory_commitments.bin_count") 73 } 74 } 75 } 76 77 #[cfg(feature = "serde_json")] 78 struct OrderEnvelopeEventBuildParts<'a, T> { 79 recipient_pubkey: &'a str, 80 message_type: RadrootsOrderEventType, 81 listing_addr: &'a str, 82 order_id: &'a str, 83 listing_event: Option<&'a RadrootsNostrEventPtr>, 84 root_event_id: Option<&'a RadrootsEventId>, 85 prev_event_id: Option<&'a RadrootsEventId>, 86 payload: &'a T, 87 } 88 89 #[cfg(feature = "serde_json")] 90 fn order_envelope_event_build<T: serde::Serialize>( 91 parts: OrderEnvelopeEventBuildParts<'_, T>, 92 ) -> Result<WireEventParts, EventEncodeError> { 93 if parts.message_type.requires_listing_snapshot() && parts.listing_event.is_none() { 94 return Err(EventEncodeError::EmptyRequiredField("listing_event.id")); 95 } 96 if parts.message_type.requires_order_chain() { 97 if parts.root_event_id.is_none() { 98 return Err(EventEncodeError::EmptyRequiredField("root_event_id")); 99 } 100 if parts.prev_event_id.is_none() { 101 return Err(EventEncodeError::EmptyRequiredField("prev_event_id")); 102 } 103 } 104 105 let envelope = RadrootsOrderEnvelope::new( 106 parts.message_type, 107 parts.listing_addr, 108 parts.order_id, 109 parts.payload, 110 ); 111 envelope.validate().map_err(map_order_envelope_error)?; 112 let content = serde_json::to_string(&envelope).map_err(|_| EventEncodeError::Json)?; 113 let tags = order_envelope_tags( 114 parts.recipient_pubkey, 115 parts.listing_addr, 116 Some(parts.order_id), 117 parts.listing_event, 118 parts.root_event_id.map(RadrootsEventId::as_str), 119 parts.prev_event_id.map(RadrootsEventId::as_str), 120 )?; 121 Ok(WireEventParts { 122 kind: parts.message_type.kind(), 123 content, 124 tags, 125 }) 126 } 127 128 #[cfg(feature = "serde_json")] 129 pub fn order_request_event_build( 130 listing_event: &RadrootsNostrEventPtr, 131 payload: &RadrootsOrderRequest, 132 ) -> Result<WireEventParts, EventEncodeError> { 133 payload.validate().map_err(map_order_payload_error)?; 134 order_envelope_event_build(OrderEnvelopeEventBuildParts { 135 recipient_pubkey: &payload.seller_pubkey, 136 message_type: RadrootsOrderEventType::OrderRequested, 137 listing_addr: &payload.listing_addr, 138 order_id: &payload.order_id, 139 listing_event: Some(listing_event), 140 root_event_id: None, 141 prev_event_id: None, 142 payload, 143 }) 144 } 145 146 #[cfg(feature = "serde_json")] 147 pub fn order_decision_event_build( 148 root_event_id: &RadrootsEventId, 149 prev_event_id: &RadrootsEventId, 150 payload: &RadrootsOrderDecision, 151 ) -> Result<WireEventParts, EventEncodeError> { 152 payload.validate().map_err(map_order_payload_error)?; 153 order_envelope_event_build(OrderEnvelopeEventBuildParts { 154 recipient_pubkey: &payload.buyer_pubkey, 155 message_type: RadrootsOrderEventType::OrderDecision, 156 listing_addr: &payload.listing_addr, 157 order_id: &payload.order_id, 158 listing_event: None, 159 root_event_id: Some(root_event_id), 160 prev_event_id: Some(prev_event_id), 161 payload, 162 }) 163 } 164 165 #[cfg(feature = "serde_json")] 166 pub fn order_revision_proposal_event_build( 167 root_event_id: &RadrootsEventId, 168 prev_event_id: &RadrootsEventId, 169 payload: &RadrootsOrderRevisionProposal, 170 ) -> Result<WireEventParts, EventEncodeError> { 171 payload.validate().map_err(map_order_payload_error)?; 172 if payload.root_event_id.as_str() != root_event_id.as_str() { 173 return Err(EventEncodeError::InvalidField("root_event_id")); 174 } 175 if payload.prev_event_id.as_str() != prev_event_id.as_str() { 176 return Err(EventEncodeError::InvalidField("prev_event_id")); 177 } 178 order_envelope_event_build(OrderEnvelopeEventBuildParts { 179 recipient_pubkey: &payload.buyer_pubkey, 180 message_type: RadrootsOrderEventType::OrderRevisionProposed, 181 listing_addr: &payload.listing_addr, 182 order_id: &payload.order_id, 183 listing_event: None, 184 root_event_id: Some(root_event_id), 185 prev_event_id: Some(prev_event_id), 186 payload, 187 }) 188 } 189 190 #[cfg(feature = "serde_json")] 191 pub fn order_revision_decision_event_build( 192 root_event_id: &RadrootsEventId, 193 prev_event_id: &RadrootsEventId, 194 payload: &RadrootsOrderRevisionDecision, 195 ) -> Result<WireEventParts, EventEncodeError> { 196 payload.validate().map_err(map_order_payload_error)?; 197 if payload.root_event_id.as_str() != root_event_id.as_str() { 198 return Err(EventEncodeError::InvalidField("root_event_id")); 199 } 200 if payload.prev_event_id.as_str() != prev_event_id.as_str() { 201 return Err(EventEncodeError::InvalidField("prev_event_id")); 202 } 203 order_envelope_event_build(OrderEnvelopeEventBuildParts { 204 recipient_pubkey: &payload.seller_pubkey, 205 message_type: RadrootsOrderEventType::OrderRevisionDecision, 206 listing_addr: &payload.listing_addr, 207 order_id: &payload.order_id, 208 listing_event: None, 209 root_event_id: Some(root_event_id), 210 prev_event_id: Some(prev_event_id), 211 payload, 212 }) 213 } 214 215 #[cfg(feature = "serde_json")] 216 pub fn order_cancellation_event_build( 217 root_event_id: &RadrootsEventId, 218 prev_event_id: &RadrootsEventId, 219 payload: &RadrootsOrderCancellation, 220 ) -> Result<WireEventParts, EventEncodeError> { 221 payload.validate().map_err(map_order_payload_error)?; 222 order_envelope_event_build(OrderEnvelopeEventBuildParts { 223 recipient_pubkey: &payload.seller_pubkey, 224 message_type: RadrootsOrderEventType::OrderCancelled, 225 listing_addr: &payload.listing_addr, 226 order_id: &payload.order_id, 227 listing_event: None, 228 root_event_id: Some(root_event_id), 229 prev_event_id: Some(prev_event_id), 230 payload, 231 }) 232 } 233 234 #[cfg(all(test, feature = "serde_json"))] 235 mod tests { 236 use super::{ 237 OrderEnvelopeEventBuildParts, map_order_envelope_error, map_order_payload_error, 238 order_envelope_event_build, 239 }; 240 use crate::error::EventEncodeError; 241 use radroots_events::{ 242 RadrootsNostrEventPtr, 243 ids::RadrootsEventId, 244 order::{RadrootsOrderEnvelopeError, RadrootsOrderEventType, RadrootsOrderPayloadError}, 245 }; 246 247 fn event_id(character: char) -> RadrootsEventId { 248 core::iter::repeat_n(character, 64) 249 .collect::<String>() 250 .parse() 251 .unwrap() 252 } 253 254 fn payload() -> serde_json::Value { 255 serde_json::json!({}) 256 } 257 258 #[test] 259 fn order_encode_error_mappers_cover_envelope_and_payload_variants() { 260 assert_empty_required( 261 map_order_envelope_error(RadrootsOrderEnvelopeError::MissingOrderId), 262 "order_id", 263 ); 264 assert_empty_required( 265 map_order_envelope_error(RadrootsOrderEnvelopeError::MissingListingAddr), 266 "listing_addr", 267 ); 268 assert_invalid_field( 269 map_order_envelope_error(RadrootsOrderEnvelopeError::InvalidVersion { 270 expected: 1, 271 got: 2, 272 }), 273 "version", 274 ); 275 assert_empty_required( 276 map_order_payload_error(RadrootsOrderPayloadError::EmptyField("buyer_pubkey")), 277 "buyer_pubkey", 278 ); 279 assert_empty_required( 280 map_order_payload_error(RadrootsOrderPayloadError::MissingItems), 281 "items", 282 ); 283 assert_invalid_field( 284 map_order_payload_error(RadrootsOrderPayloadError::InvalidItemBinCount { index: 0 }), 285 "items.bin_count", 286 ); 287 assert_empty_required( 288 map_order_payload_error(RadrootsOrderPayloadError::MissingEconomicItems), 289 "economics.items", 290 ); 291 assert_invalid_field( 292 map_order_payload_error(RadrootsOrderPayloadError::InvalidEconomicItemBinCount { 293 index: 0, 294 }), 295 "economics.items.bin_count", 296 ); 297 assert_invalid_field( 298 map_order_payload_error(RadrootsOrderPayloadError::InvalidEconomicItemQuantity { 299 index: 0, 300 }), 301 "economics.items.quantity_amount", 302 ); 303 assert_invalid_field( 304 map_order_payload_error(RadrootsOrderPayloadError::InvalidEconomicItemPrice { 305 index: 0, 306 }), 307 "economics.items.unit_price_amount", 308 ); 309 assert_invalid_field( 310 map_order_payload_error(RadrootsOrderPayloadError::InvalidEconomicItemSubtotal { 311 index: 0, 312 }), 313 "economics.items.line_subtotal", 314 ); 315 for error in [ 316 RadrootsOrderPayloadError::InvalidEconomicLineAmount { 317 field: "adjustments.amount", 318 index: 0, 319 }, 320 RadrootsOrderPayloadError::InvalidEconomicLineKind { 321 field: "discounts.kind", 322 index: 0, 323 }, 324 RadrootsOrderPayloadError::InvalidEconomicLineEffect { 325 field: "discounts.effect", 326 index: 0, 327 }, 328 RadrootsOrderPayloadError::InvalidEconomicCurrency { 329 field: "subtotal.currency", 330 }, 331 RadrootsOrderPayloadError::InvalidEconomicOrdering { 332 field: "adjustments", 333 }, 334 RadrootsOrderPayloadError::InvalidEconomicTotal { field: "total" }, 335 RadrootsOrderPayloadError::InvalidOrderEconomicsBinding { field: "items" }, 336 ] { 337 assert!(matches!( 338 map_order_payload_error(error), 339 EventEncodeError::InvalidField(_) 340 )); 341 } 342 assert_invalid_field( 343 map_order_payload_error(RadrootsOrderPayloadError::InvalidQuoteVersion), 344 "economics.quote_version", 345 ); 346 assert_empty_required( 347 map_order_payload_error(RadrootsOrderPayloadError::MissingInventoryCommitments), 348 "inventory_commitments", 349 ); 350 assert_invalid_field( 351 map_order_payload_error(RadrootsOrderPayloadError::InvalidInventoryCommitmentCount { 352 index: 0, 353 }), 354 "inventory_commitments.bin_count", 355 ); 356 } 357 358 #[test] 359 fn order_envelope_event_build_requires_context_tags_by_message_type() { 360 let payload = payload(); 361 let root_event_id = event_id('1'); 362 let prev_event_id = event_id('2'); 363 364 let missing_listing_event = order_envelope_event_build(OrderEnvelopeEventBuildParts { 365 recipient_pubkey: "recipient", 366 message_type: RadrootsOrderEventType::OrderRequested, 367 listing_addr: "listing-address", 368 order_id: "order-1", 369 listing_event: None, 370 root_event_id: None, 371 prev_event_id: None, 372 payload: &payload, 373 }) 374 .unwrap_err(); 375 assert_empty_required(missing_listing_event, "listing_event.id"); 376 377 let missing_root = order_envelope_event_build(OrderEnvelopeEventBuildParts { 378 recipient_pubkey: "recipient", 379 message_type: RadrootsOrderEventType::OrderDecision, 380 listing_addr: "listing-address", 381 order_id: "order-1", 382 listing_event: None, 383 root_event_id: None, 384 prev_event_id: Some(&prev_event_id), 385 payload: &payload, 386 }) 387 .unwrap_err(); 388 assert_empty_required(missing_root, "root_event_id"); 389 390 let missing_prev = order_envelope_event_build(OrderEnvelopeEventBuildParts { 391 recipient_pubkey: "recipient", 392 message_type: RadrootsOrderEventType::OrderDecision, 393 listing_addr: "listing-address", 394 order_id: "order-1", 395 listing_event: None, 396 root_event_id: Some(&root_event_id), 397 prev_event_id: None, 398 payload: &payload, 399 }) 400 .unwrap_err(); 401 assert_empty_required(missing_prev, "prev_event_id"); 402 403 let invalid_listing_event = order_envelope_event_build(OrderEnvelopeEventBuildParts { 404 recipient_pubkey: "recipient", 405 message_type: RadrootsOrderEventType::OrderRequested, 406 listing_addr: "listing-address", 407 order_id: "order-1", 408 listing_event: Some(&RadrootsNostrEventPtr { 409 id: String::new(), 410 relays: None, 411 }), 412 root_event_id: None, 413 prev_event_id: None, 414 payload: &payload, 415 }) 416 .unwrap_err(); 417 assert_empty_required(invalid_listing_event, "listing_event.id"); 418 } 419 420 fn assert_empty_required(error: EventEncodeError, field: &'static str) { 421 match error { 422 EventEncodeError::EmptyRequiredField(found) => assert_eq!(found, field), 423 other => panic!("unexpected error: {other:?}"), 424 } 425 } 426 427 fn assert_invalid_field(error: EventEncodeError, field: &'static str) { 428 match error { 429 EventEncodeError::InvalidField(found) => assert_eq!(found, field), 430 other => panic!("unexpected error: {other:?}"), 431 } 432 } 433 }