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