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