lib

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

mod.rs (6662B)


      1 pub mod decode;
      2 pub mod encode;
      3 
      4 use crate::d_tag::is_d_tag_base64url;
      5 
      6 fn list_set_requires_base64(d_tag: &str) -> bool {
      7     d_tag.starts_with("farm:") || d_tag.starts_with("coop:") || d_tag.starts_with("resource:")
      8 }
      9 
     10 fn list_set_base64_id_is_valid(d_tag: &str) -> bool {
     11     if !list_set_requires_base64(d_tag) {
     12         return true;
     13     }
     14     let mut parts = d_tag.splitn(3, ':');
     15     let _ = parts.next();
     16     let id = parts.next().unwrap_or("");
     17     let suffix = parts.next().unwrap_or("");
     18     !id.trim().is_empty() && !suffix.trim().is_empty() && is_d_tag_base64url(id)
     19 }
     20 
     21 #[cfg(test)]
     22 mod tests {
     23     use super::{decode::list_set_from_tags, encode::list_set_build_tags};
     24     use crate::error::{EventEncodeError, EventParseError};
     25     use radroots_events::{
     26         kinds::{KIND_LIST_SET_FOLLOW, KIND_POST},
     27         list::RadrootsListEntry,
     28         list_set::RadrootsListSet,
     29     };
     30 
     31     #[test]
     32     fn list_set_tags_round_trip() {
     33         let list = RadrootsListSet {
     34             d_tag: "members.owners".to_string(),
     35             content: "".to_string(),
     36             entries: vec![
     37                 RadrootsListEntry {
     38                     tag: "p".to_string(),
     39                     values: vec!["owner_pubkey".to_string()],
     40                 },
     41                 RadrootsListEntry {
     42                     tag: "p".to_string(),
     43                     values: vec!["worker_pubkey".to_string(), "wss://relay".to_string()],
     44                 },
     45             ],
     46             title: Some("Owners".to_string()),
     47             description: None,
     48             image: None,
     49         };
     50         let tags = list_set_build_tags(&list).expect("build tags");
     51         let parsed = list_set_from_tags(KIND_LIST_SET_FOLLOW, list.content.clone(), &tags)
     52             .expect("parse list set");
     53         assert_eq!(parsed.d_tag, list.d_tag);
     54         assert_eq!(parsed.title, list.title);
     55         assert_eq!(parsed.entries.len(), list.entries.len());
     56         assert_eq!(parsed.entries[0].values[0], "owner_pubkey");
     57     }
     58 
     59     #[test]
     60     fn list_set_rejects_invalid_farm_d_tag_on_encode() {
     61         let list = RadrootsListSet {
     62             d_tag: "farm:invalid:members".to_string(),
     63             content: "".to_string(),
     64             entries: vec![RadrootsListEntry {
     65                 tag: "p".to_string(),
     66                 values: vec!["pubkey".to_string()],
     67             }],
     68             title: None,
     69             description: None,
     70             image: None,
     71         };
     72         let err = list_set_build_tags(&list).expect_err("expected invalid d_tag");
     73         assert!(matches!(err, EventEncodeError::InvalidField("d_tag")));
     74     }
     75 
     76     #[test]
     77     fn list_set_rejects_invalid_farm_d_tag_on_decode() {
     78         let tags = vec![
     79             vec!["d".to_string(), "farm:invalid:members".to_string()],
     80             vec!["p".to_string(), "pubkey".to_string()],
     81         ];
     82         let err = list_set_from_tags(KIND_LIST_SET_FOLLOW, "".to_string(), &tags)
     83             .expect_err("expected invalid d_tag");
     84         assert!(matches!(err, EventParseError::InvalidTag("d")));
     85     }
     86 
     87     #[test]
     88     fn list_set_accepts_resource_base64_d_tag() {
     89         let list = RadrootsListSet {
     90             d_tag: "resource:AAAAAAAAAAAAAAAAAAAAAA:members".to_string(),
     91             content: "".to_string(),
     92             entries: vec![RadrootsListEntry {
     93                 tag: "p".to_string(),
     94                 values: vec!["pubkey".to_string()],
     95             }],
     96             title: None,
     97             description: None,
     98             image: None,
     99         };
    100         let tags = list_set_build_tags(&list).expect("build tags");
    101         let parsed = list_set_from_tags(KIND_LIST_SET_FOLLOW, list.content.clone(), &tags)
    102             .expect("parse list set");
    103         assert_eq!(parsed.d_tag, list.d_tag);
    104     }
    105 
    106     #[test]
    107     fn list_set_rejects_empty_prefixed_id_or_suffix() {
    108         let list = RadrootsListSet {
    109             d_tag: "farm::members".to_string(),
    110             content: "".to_string(),
    111             entries: vec![RadrootsListEntry {
    112                 tag: "p".to_string(),
    113                 values: vec!["pubkey".to_string()],
    114             }],
    115             title: None,
    116             description: None,
    117             image: None,
    118         };
    119         let err = list_set_build_tags(&list).expect_err("expected invalid d_tag");
    120         assert!(matches!(err, EventEncodeError::InvalidField("d_tag")));
    121 
    122         let tags = vec![
    123             vec!["d".to_string(), "coop:AAAAAAAAAAAAAAAAAAAAAA:".to_string()],
    124             vec!["p".to_string(), "pubkey".to_string()],
    125         ];
    126         let err = list_set_from_tags(KIND_LIST_SET_FOLLOW, "".to_string(), &tags)
    127             .expect_err("expected invalid d_tag");
    128         assert!(matches!(err, EventParseError::InvalidTag("d")));
    129     }
    130 
    131     #[test]
    132     fn list_set_decode_ignores_short_tags() {
    133         let tags = vec![
    134             vec!["d".to_string(), "members.owners".to_string()],
    135             vec!["p".to_string(), "owner".to_string()],
    136             vec!["subject".to_string()],
    137         ];
    138         let parsed =
    139             list_set_from_tags(KIND_LIST_SET_FOLLOW, "private".to_string(), &tags).expect("parsed");
    140         assert_eq!(parsed.d_tag, "members.owners");
    141         assert_eq!(parsed.entries.len(), 1);
    142         assert_eq!(parsed.entries[0].tag, "p");
    143     }
    144 
    145     #[test]
    146     fn list_set_decode_rejects_empty_entry_value() {
    147         let tags = vec![
    148             vec!["d".to_string(), "members.owners".to_string()],
    149             vec!["p".to_string(), " ".to_string()],
    150         ];
    151         let err = list_set_from_tags(KIND_LIST_SET_FOLLOW, "private".to_string(), &tags)
    152             .expect_err("expected invalid entry tag");
    153         assert!(matches!(err, EventParseError::InvalidTag("tag")));
    154     }
    155 
    156     #[test]
    157     fn list_set_decode_rejects_invalid_kind() {
    158         let tags = vec![
    159             vec!["d".to_string(), "members.owners".to_string()],
    160             vec!["p".to_string(), "owner".to_string()],
    161         ];
    162         let err = list_set_from_tags(KIND_POST, "private".to_string(), &tags)
    163             .expect_err("expected invalid kind");
    164         assert!(matches!(
    165             err,
    166             EventParseError::InvalidKind {
    167                 expected: "nip51 list set kind",
    168                 got: KIND_POST
    169             }
    170         ));
    171     }
    172 
    173     #[test]
    174     fn list_set_decode_rejects_empty_tag_name() {
    175         let tags = vec![
    176             vec!["d".to_string(), "members.owners".to_string()],
    177             vec!["".to_string(), "owner".to_string()],
    178         ];
    179         let err = list_set_from_tags(KIND_LIST_SET_FOLLOW, "private".to_string(), &tags)
    180             .expect_err("expected invalid empty tag name");
    181         assert!(matches!(err, EventParseError::InvalidTag("tag")));
    182     }
    183 }