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