encode.rs (5390B)
1 #[cfg(not(feature = "std"))] 2 use alloc::{ 3 string::{String, ToString}, 4 vec::Vec, 5 }; 6 7 use radroots_events::{ 8 kinds::KIND_RESOURCE_AREA, resource_cap::RadrootsResourceHarvestCap, tags::TAG_D, 9 }; 10 11 use crate::d_tag::validate_d_tag; 12 use crate::error::EventEncodeError; 13 14 #[cfg(feature = "serde_json")] 15 use crate::wire::WireEventParts; 16 #[cfg(feature = "serde_json")] 17 use radroots_events::kinds::KIND_RESOURCE_HARVEST_CAP; 18 19 const TAG_A: &str = "a"; 20 const TAG_P: &str = "p"; 21 const TAG_T: &str = "t"; 22 const TAG_KEY: &str = "key"; 23 const TAG_CATEGORY: &str = "category"; 24 const TAG_START: &str = "start"; 25 const TAG_END: &str = "end"; 26 27 fn push_tag(tags: &mut Vec<Vec<String>>, key: &str, value: &str) { 28 tags.push(vec![key.to_string(), value.to_string()]); 29 } 30 31 fn resource_area_address(cap: &RadrootsResourceHarvestCap) -> Result<String, EventEncodeError> { 32 let area = &cap.resource_area; 33 if area.pubkey.trim().is_empty() { 34 return Err(EventEncodeError::EmptyRequiredField("resource_area.pubkey")); 35 } 36 if area.d_tag.trim().is_empty() { 37 return Err(EventEncodeError::EmptyRequiredField("resource_area.d_tag")); 38 } 39 validate_d_tag(&area.d_tag, "resource_area.d_tag")?; 40 let mut addr = String::new(); 41 addr.push_str(&KIND_RESOURCE_AREA.to_string()); 42 addr.push(':'); 43 addr.push_str(&area.pubkey); 44 addr.push(':'); 45 addr.push_str(&area.d_tag); 46 Ok(addr) 47 } 48 49 pub fn resource_harvest_cap_build_tags( 50 cap: &RadrootsResourceHarvestCap, 51 ) -> Result<Vec<Vec<String>>, EventEncodeError> { 52 if cap.d_tag.trim().is_empty() { 53 return Err(EventEncodeError::EmptyRequiredField("d_tag")); 54 } 55 validate_d_tag(&cap.d_tag, "d_tag")?; 56 if cap.product.key.trim().is_empty() { 57 return Err(EventEncodeError::EmptyRequiredField("product.key")); 58 } 59 let mut tags = Vec::new(); 60 push_tag(&mut tags, TAG_D, &cap.d_tag); 61 let addr = resource_area_address(cap)?; 62 push_tag(&mut tags, TAG_A, &addr); 63 push_tag(&mut tags, TAG_P, &cap.resource_area.pubkey); 64 push_tag(&mut tags, TAG_KEY, &cap.product.key); 65 if let Some(category) = cap.product.category.as_deref() 66 && !category.trim().is_empty() 67 { 68 push_tag(&mut tags, TAG_CATEGORY, category); 69 } 70 push_tag(&mut tags, TAG_START, &cap.start.to_string()); 71 push_tag(&mut tags, TAG_END, &cap.end.to_string()); 72 if let Some(items) = cap.tags.as_ref() { 73 for item in items.iter().filter(|v| !v.trim().is_empty()) { 74 push_tag(&mut tags, TAG_T, item); 75 } 76 } 77 Ok(tags) 78 } 79 80 #[cfg(feature = "serde_json")] 81 pub fn to_wire_parts(cap: &RadrootsResourceHarvestCap) -> Result<WireEventParts, EventEncodeError> { 82 to_wire_parts_with_kind(cap, KIND_RESOURCE_HARVEST_CAP) 83 } 84 85 #[cfg(feature = "serde_json")] 86 pub fn to_wire_parts_with_kind( 87 cap: &RadrootsResourceHarvestCap, 88 kind: u32, 89 ) -> Result<WireEventParts, EventEncodeError> { 90 if kind != KIND_RESOURCE_HARVEST_CAP { 91 return Err(EventEncodeError::InvalidKind(kind)); 92 } 93 let tags = resource_harvest_cap_build_tags(cap)?; 94 let content = serde_json::to_string(cap).map_err(|_| EventEncodeError::Json)?; 95 Ok(WireEventParts { 96 kind, 97 content, 98 tags, 99 }) 100 } 101 102 #[cfg(test)] 103 mod tests { 104 use super::*; 105 use crate::test_fixtures::FIXTURE_ALICE_PUBLIC_KEY_HEX; 106 use radroots_core::{RadrootsCoreDecimal, RadrootsCoreQuantity, RadrootsCoreUnit}; 107 use radroots_events::resource_area::RadrootsResourceAreaRef; 108 use radroots_events::resource_cap::RadrootsResourceHarvestProduct; 109 110 fn sample_cap_with_category(category: Option<&str>) -> RadrootsResourceHarvestCap { 111 RadrootsResourceHarvestCap { 112 d_tag: "AAAAAAAAAAAAAAAAAAAABA".to_string(), 113 resource_area: RadrootsResourceAreaRef { 114 pubkey: FIXTURE_ALICE_PUBLIC_KEY_HEX.to_string(), 115 d_tag: "AAAAAAAAAAAAAAAAAAAAAw".to_string(), 116 }, 117 product: RadrootsResourceHarvestProduct { 118 key: "nutmeg".to_string(), 119 category: category.map(|value| value.to_string()), 120 }, 121 start: 1, 122 end: 2, 123 cap_quantity: RadrootsCoreQuantity::new( 124 RadrootsCoreDecimal::from(1000u32), 125 RadrootsCoreUnit::MassG, 126 ), 127 display_amount: None, 128 display_unit: None, 129 display_label: None, 130 tags: None, 131 } 132 } 133 134 #[test] 135 fn resource_harvest_cap_build_tags_omits_blank_category() { 136 let tags = resource_harvest_cap_build_tags(&sample_cap_with_category(Some(" "))) 137 .expect("resource harvest cap tags"); 138 assert!( 139 !tags 140 .iter() 141 .any(|tag| tag.first().map(|v| v.as_str()) == Some("category")) 142 ); 143 144 let tags = resource_harvest_cap_build_tags(&sample_cap_with_category(None)) 145 .expect("resource harvest cap tags"); 146 assert!( 147 !tags 148 .iter() 149 .any(|tag| tag.first().map(|v| v.as_str()) == Some("category")) 150 ); 151 152 let tags = resource_harvest_cap_build_tags(&sample_cap_with_category(Some("spice"))) 153 .expect("resource harvest cap tags"); 154 assert!( 155 tags.iter() 156 .any(|tag| tag.first().map(|v| v.as_str()) == Some("category")) 157 ); 158 } 159 }