lib

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

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 }