lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }