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 }