lib

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

mod.rs (7231B)


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