list_sets.rs (10450B)
1 #![forbid(unsafe_code)] 2 3 #[cfg(not(feature = "std"))] 4 use alloc::{ 5 format, 6 string::{String, ToString}, 7 vec, 8 vec::Vec, 9 }; 10 11 use radroots_events::farm::RadrootsFarmRef; 12 use radroots_events::kinds::{KIND_FARM, KIND_PLOT}; 13 use radroots_events::list::RadrootsListEntry; 14 use radroots_events::list_set::RadrootsListSet; 15 use radroots_events::plot::RadrootsPlotRef; 16 17 use crate::d_tag::validate_d_tag; 18 use crate::error::EventEncodeError; 19 20 fn resource_list_set_id(area_id: &str, suffix: &str) -> Result<String, EventEncodeError> { 21 let area_id = area_id.trim(); 22 if area_id.is_empty() { 23 return Err(EventEncodeError::EmptyRequiredField("area_id")); 24 } 25 validate_d_tag(area_id, "area_id")?; 26 Ok(format!("resource:{area_id}:{suffix}")) 27 } 28 29 fn list_entries<I, S>(tag: &str, values: I) -> Result<Vec<RadrootsListEntry>, EventEncodeError> 30 where 31 I: IntoIterator<Item = S>, 32 S: AsRef<str>, 33 { 34 let mut entries = Vec::new(); 35 for value in values { 36 let value = value.as_ref().trim(); 37 if value.is_empty() { 38 return Err(EventEncodeError::EmptyRequiredField("entry.values")); 39 } 40 entries.push(RadrootsListEntry { 41 tag: tag.to_string(), 42 values: vec![value.to_string()], 43 }); 44 } 45 Ok(entries) 46 } 47 48 fn farm_address(farm: &RadrootsFarmRef) -> Result<String, EventEncodeError> { 49 if farm.pubkey.trim().is_empty() { 50 return Err(EventEncodeError::EmptyRequiredField("farm.pubkey")); 51 } 52 if farm.d_tag.trim().is_empty() { 53 return Err(EventEncodeError::EmptyRequiredField("farm.d_tag")); 54 } 55 validate_d_tag(&farm.d_tag, "farm.d_tag")?; 56 let mut addr = String::new(); 57 addr.push_str(&KIND_FARM.to_string()); 58 addr.push(':'); 59 addr.push_str(&farm.pubkey); 60 addr.push(':'); 61 addr.push_str(&farm.d_tag); 62 Ok(addr) 63 } 64 65 fn plot_address(plot: &RadrootsPlotRef) -> Result<String, EventEncodeError> { 66 if plot.pubkey.trim().is_empty() { 67 return Err(EventEncodeError::EmptyRequiredField("plot.pubkey")); 68 } 69 if plot.d_tag.trim().is_empty() { 70 return Err(EventEncodeError::EmptyRequiredField("plot.d_tag")); 71 } 72 validate_d_tag(&plot.d_tag, "plot.d_tag")?; 73 let mut addr = String::new(); 74 addr.push_str(&KIND_PLOT.to_string()); 75 addr.push(':'); 76 addr.push_str(&plot.pubkey); 77 addr.push(':'); 78 addr.push_str(&plot.d_tag); 79 Ok(addr) 80 } 81 82 pub fn resource_area_members_farms_list_set<I>( 83 area_id: &str, 84 farms: I, 85 ) -> Result<RadrootsListSet, EventEncodeError> 86 where 87 I: IntoIterator<Item = RadrootsFarmRef>, 88 { 89 let mut entries = Vec::new(); 90 for farm in farms { 91 let address = farm_address(&farm)?; 92 entries.push(RadrootsListEntry { 93 tag: "a".to_string(), 94 values: vec![address], 95 }); 96 entries.push(RadrootsListEntry { 97 tag: "p".to_string(), 98 values: vec![farm.pubkey], 99 }); 100 } 101 Ok(RadrootsListSet { 102 d_tag: resource_list_set_id(area_id, "members.farms")?, 103 content: String::new(), 104 entries, 105 title: None, 106 description: None, 107 image: None, 108 }) 109 } 110 111 pub fn resource_area_members_plots_list_set<I>( 112 area_id: &str, 113 plots: I, 114 ) -> Result<RadrootsListSet, EventEncodeError> 115 where 116 I: IntoIterator<Item = RadrootsPlotRef>, 117 { 118 let mut entries = Vec::new(); 119 for plot in plots { 120 let address = plot_address(&plot)?; 121 entries.push(RadrootsListEntry { 122 tag: "a".to_string(), 123 values: vec![address], 124 }); 125 entries.push(RadrootsListEntry { 126 tag: "p".to_string(), 127 values: vec![plot.pubkey], 128 }); 129 } 130 Ok(RadrootsListSet { 131 d_tag: resource_list_set_id(area_id, "members.plots")?, 132 content: String::new(), 133 entries, 134 title: None, 135 description: None, 136 image: None, 137 }) 138 } 139 140 pub fn resource_area_stewards_list_set<I, S>( 141 area_id: &str, 142 stewards: I, 143 ) -> Result<RadrootsListSet, EventEncodeError> 144 where 145 I: IntoIterator<Item = S>, 146 S: AsRef<str>, 147 { 148 Ok(RadrootsListSet { 149 d_tag: resource_list_set_id(area_id, "members.stewards")?, 150 content: String::new(), 151 entries: list_entries("p", stewards)?, 152 title: None, 153 description: None, 154 image: None, 155 }) 156 } 157 158 #[cfg(test)] 159 mod tests { 160 use super::*; 161 use crate::test_fixtures::{FIXTURE_ALICE_PUBLIC_KEY_HEX, FIXTURE_BOB_PUBLIC_KEY_HEX}; 162 163 #[test] 164 fn resource_list_set_id_validates_area_id() { 165 let err = 166 resource_list_set_id(" ", "members.farms").expect_err("expected empty area_id error"); 167 assert!(matches!( 168 err, 169 EventEncodeError::EmptyRequiredField("area_id") 170 )); 171 } 172 173 #[test] 174 fn list_entries_rejects_empty_values() { 175 let err = list_entries("p", [" "]).expect_err("expected empty entry error"); 176 assert!(matches!( 177 err, 178 EventEncodeError::EmptyRequiredField("entry.values") 179 )); 180 } 181 182 #[test] 183 fn list_entries_accepts_empty_iterators() { 184 let entries = list_entries::<_, &str>("p", core::iter::empty()) 185 .expect("empty iterators should be accepted"); 186 assert!(entries.is_empty()); 187 } 188 189 #[test] 190 fn list_entries_cover_string_iterators() { 191 let entries = list_entries("p", vec!["steward".to_string()]).expect("valid string entries"); 192 assert_eq!(entries.len(), 1); 193 assert_eq!(entries[0].values[0], "steward"); 194 195 let err = list_entries("p", vec![" ".to_string()]).expect_err("blank string entry"); 196 assert!(matches!( 197 err, 198 EventEncodeError::EmptyRequiredField("entry.values") 199 )); 200 } 201 202 #[test] 203 fn farm_and_plot_address_helpers_reject_empty_d_tags() { 204 let err = farm_address(&RadrootsFarmRef { 205 pubkey: " ".to_string(), 206 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 207 }) 208 .expect_err("expected farm pubkey error"); 209 assert!(matches!( 210 err, 211 EventEncodeError::EmptyRequiredField("farm.pubkey") 212 )); 213 214 let err = farm_address(&RadrootsFarmRef { 215 pubkey: "farm_pubkey".to_string(), 216 d_tag: " ".to_string(), 217 }) 218 .expect_err("expected farm d_tag error"); 219 assert!(matches!( 220 err, 221 EventEncodeError::EmptyRequiredField("farm.d_tag") 222 )); 223 224 let err = plot_address(&RadrootsPlotRef { 225 pubkey: " ".to_string(), 226 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 227 }) 228 .expect_err("expected plot pubkey error"); 229 assert!(matches!( 230 err, 231 EventEncodeError::EmptyRequiredField("plot.pubkey") 232 )); 233 234 let err = plot_address(&RadrootsPlotRef { 235 pubkey: "plot_pubkey".to_string(), 236 d_tag: " ".to_string(), 237 }) 238 .expect_err("expected plot d_tag error"); 239 assert!(matches!( 240 err, 241 EventEncodeError::EmptyRequiredField("plot.d_tag") 242 )); 243 } 244 245 #[test] 246 fn resource_area_list_set_builders_cover_success_and_error_paths() { 247 let area_id = "AAAAAAAAAAAAAAAAAAAAAA"; 248 let farm_pubkey = FIXTURE_ALICE_PUBLIC_KEY_HEX; 249 let plot_pubkey = FIXTURE_BOB_PUBLIC_KEY_HEX; 250 251 let err = resource_area_members_farms_list_set( 252 "invalid", 253 vec![RadrootsFarmRef { 254 pubkey: farm_pubkey.to_string(), 255 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 256 }], 257 ) 258 .expect_err("expected invalid area_id"); 259 assert!(matches!(err, EventEncodeError::InvalidField("area_id"))); 260 261 let farms = resource_area_members_farms_list_set( 262 area_id, 263 vec![RadrootsFarmRef { 264 pubkey: farm_pubkey.to_string(), 265 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 266 }], 267 ) 268 .expect("resource area farms list set"); 269 assert_eq!(farms.d_tag, "resource:AAAAAAAAAAAAAAAAAAAAAA:members.farms"); 270 assert_eq!(farms.entries.len(), 2); 271 assert_eq!(farms.entries[0].tag, "a"); 272 assert_eq!(farms.entries[1].tag, "p"); 273 274 let err = resource_area_members_farms_list_set( 275 area_id, 276 vec![RadrootsFarmRef { 277 pubkey: farm_pubkey.to_string(), 278 d_tag: "invalid".to_string(), 279 }], 280 ) 281 .expect_err("expected invalid farm d_tag"); 282 assert!(matches!(err, EventEncodeError::InvalidField("farm.d_tag"))); 283 284 let plots = resource_area_members_plots_list_set( 285 area_id, 286 vec![RadrootsPlotRef { 287 pubkey: plot_pubkey.to_string(), 288 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 289 }], 290 ) 291 .expect("resource area plots list set"); 292 assert_eq!(plots.d_tag, "resource:AAAAAAAAAAAAAAAAAAAAAA:members.plots"); 293 assert_eq!(plots.entries.len(), 2); 294 assert_eq!(plots.entries[0].tag, "a"); 295 assert_eq!(plots.entries[1].tag, "p"); 296 297 let err = resource_area_members_plots_list_set( 298 "invalid", 299 vec![RadrootsPlotRef { 300 pubkey: plot_pubkey.to_string(), 301 d_tag: "AAAAAAAAAAAAAAAAAAAAAA".to_string(), 302 }], 303 ) 304 .expect_err("expected invalid area_id for plots list set"); 305 assert!(matches!(err, EventEncodeError::InvalidField("area_id"))); 306 307 let err = resource_area_members_plots_list_set( 308 area_id, 309 vec![RadrootsPlotRef { 310 pubkey: plot_pubkey.to_string(), 311 d_tag: "invalid".to_string(), 312 }], 313 ) 314 .expect_err("expected invalid plot d_tag"); 315 assert!(matches!(err, EventEncodeError::InvalidField("plot.d_tag"))); 316 317 let stewards = 318 resource_area_stewards_list_set(area_id, ["steward-a"]).expect("stewards list set"); 319 assert_eq!( 320 stewards.d_tag, 321 "resource:AAAAAAAAAAAAAAAAAAAAAA:members.stewards" 322 ); 323 assert_eq!(stewards.entries[0].tag, "p"); 324 let err = resource_area_stewards_list_set(area_id, [" "]) 325 .expect_err("expected invalid steward entry"); 326 assert!(matches!( 327 err, 328 EventEncodeError::EmptyRequiredField("entry.values") 329 )); 330 } 331 }