identity.rs (6003B)
1 use std::{ 2 borrow::Borrow, 3 fmt::{self, Display}, 4 }; 5 6 use hashbrown::HashSet; 7 use nostr::types::RelayUrl; 8 use url::Url; 9 use uuid::Uuid; 10 11 use crate::Error; 12 13 #[derive(Eq, PartialEq, Hash, Clone, Debug)] 14 pub enum RelayId { 15 Websocket(NormRelayUrl), 16 Multicast, 17 } 18 19 #[derive(Clone, Copy, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)] 20 pub struct OutboxSubId(pub u64); 21 22 #[derive(Clone, Copy, PartialEq, Eq, Debug)] 23 pub enum RelayReqStatus { 24 InitialQuery, 25 Eose, 26 Closed, 27 } 28 29 #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] 30 pub struct RelayReqId(pub String); 31 32 impl RelayReqId { 33 pub fn byte_len() -> usize { 34 uuid::fmt::Hyphenated::LENGTH 35 } 36 } 37 38 impl Default for RelayReqId { 39 fn default() -> Self { 40 Self(Uuid::new_v4().to_string()) 41 } 42 } 43 44 impl From<String> for RelayReqId { 45 fn from(s: String) -> Self { 46 Self(s) 47 } 48 } 49 50 impl From<RelayReqId> for String { 51 fn from(value: RelayReqId) -> Self { 52 value.0 53 } 54 } 55 56 impl From<&str> for RelayReqId { 57 fn from(s: &str) -> Self { 58 Self(s.to_owned()) 59 } 60 } 61 62 impl From<Uuid> for RelayReqId { 63 fn from(value: Uuid) -> Self { 64 RelayReqId(value.to_string()) 65 } 66 } 67 68 impl std::fmt::Display for RelayReqId { 69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 70 self.0.fmt(f) 71 } 72 } 73 74 impl Borrow<str> for RelayReqId { 75 fn borrow(&self) -> &str { 76 &self.0 77 } 78 } 79 80 #[derive(Eq, PartialEq, Hash, Clone, Debug, PartialOrd, Ord)] 81 pub struct NormRelayUrl { 82 url: RelayUrl, 83 } 84 85 impl NormRelayUrl { 86 pub fn new(url: &str) -> Result<Self, Error> { 87 Ok(Self { 88 url: nostr::RelayUrl::parse(canonicalize_url(url.to_owned())) 89 .map_err(|_| Error::InvalidRelayUrl)?, 90 }) 91 } 92 } 93 94 impl Display for NormRelayUrl { 95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 96 write!(f, "{}", self.url) 97 } 98 } 99 100 impl From<NormRelayUrl> for RelayUrl { 101 fn from(value: NormRelayUrl) -> Self { 102 value.url 103 } 104 } 105 106 impl From<RelayUrl> for NormRelayUrl { 107 fn from(url: RelayUrl) -> Self { 108 Self { url } 109 } 110 } 111 112 #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)] 113 pub enum RelayType { 114 Compaction, 115 Transparent, 116 } 117 118 #[derive(Default, Clone, Debug)] 119 pub struct RelayUrlPkgs { 120 pub urls: HashSet<NormRelayUrl>, 121 pub use_transparent: bool, 122 } 123 124 impl RelayUrlPkgs { 125 pub fn iter(&self) -> impl Iterator<Item = &NormRelayUrl> { 126 self.urls.iter() 127 } 128 129 pub fn new(urls: HashSet<NormRelayUrl>) -> Self { 130 Self { 131 urls, 132 use_transparent: false, 133 } 134 } 135 } 136 137 // standardize the format (ie, trailing slashes) 138 fn canonicalize_url(url: String) -> String { 139 match Url::parse(&url) { 140 Ok(parsed_url) => parsed_url.to_string(), 141 Err(_) => url, // If parsing fails, return the original URL. 142 } 143 } 144 145 #[cfg(test)] 146 mod tests { 147 use super::*; 148 149 // ==================== NormRelayUrl tests ==================== 150 151 #[test] 152 fn norm_relay_url_creates_valid_url() { 153 let url = NormRelayUrl::new("wss://relay.example.com"); 154 assert!(url.is_ok()); 155 } 156 157 #[test] 158 fn norm_relay_url_handles_trailing_slash() { 159 let url1 = NormRelayUrl::new("wss://relay.example.com/").unwrap(); 160 let url2 = NormRelayUrl::new("wss://relay.example.com").unwrap(); 161 // Both should canonicalize to the same thing 162 assert_eq!(url1.to_string(), url2.to_string()); 163 } 164 165 #[test] 166 fn norm_relay_url_rejects_invalid() { 167 assert!(NormRelayUrl::new("not-a-url").is_err()); 168 } 169 170 #[test] 171 fn norm_relay_url_rejects_http() { 172 // nostr relay URLs must be ws:// or wss:// 173 assert!(NormRelayUrl::new("http://relay.example.com").is_err()); 174 } 175 176 #[test] 177 fn norm_relay_url_equality() { 178 let url1 = NormRelayUrl::new("wss://relay.example.com").unwrap(); 179 let url2 = NormRelayUrl::new("wss://relay.example.com").unwrap(); 180 assert_eq!(url1, url2); 181 } 182 183 #[test] 184 fn norm_relay_url_hash_consistency() { 185 use std::collections::HashSet; 186 187 let url1 = NormRelayUrl::new("wss://relay.example.com").unwrap(); 188 let url2 = NormRelayUrl::new("wss://relay.example.com").unwrap(); 189 190 let mut set = HashSet::new(); 191 set.insert(url1); 192 assert!(set.contains(&url2)); 193 } 194 195 // ==================== RelayUrlPkgs tests ==================== 196 197 #[test] 198 fn relay_url_pkgs_default_not_transparent() { 199 let pkgs = RelayUrlPkgs::default(); 200 assert!(!pkgs.use_transparent); 201 assert!(pkgs.urls.is_empty()); 202 } 203 204 #[test] 205 fn relay_url_pkgs_new_sets_urls() { 206 let mut urls = HashSet::new(); 207 urls.insert(NormRelayUrl::new("wss://relay1.example.com").unwrap()); 208 urls.insert(NormRelayUrl::new("wss://relay2.example.com").unwrap()); 209 210 let pkgs = RelayUrlPkgs::new(urls); 211 assert_eq!(pkgs.urls.len(), 2); 212 assert!(!pkgs.use_transparent); 213 } 214 215 #[test] 216 fn relay_url_pkgs_iter() { 217 let mut urls = HashSet::new(); 218 urls.insert(NormRelayUrl::new("wss://relay1.example.com").unwrap()); 219 220 let pkgs = RelayUrlPkgs::new(urls); 221 assert_eq!(pkgs.iter().count(), 1); 222 } 223 224 // ==================== RelayREQId tests ==================== 225 226 #[test] 227 fn relay_req_id_default_generates_uuid() { 228 let id1 = RelayReqId::default(); 229 let id2 = RelayReqId::default(); 230 // Each default should generate a unique UUID 231 assert_ne!(id1, id2); 232 } 233 234 // ==================== SubRequestId tests ==================== 235 236 #[test] 237 fn sub_request_id_equality() { 238 let id1 = OutboxSubId(42); 239 let id2 = OutboxSubId(42); 240 let id3 = OutboxSubId(43); 241 242 assert_eq!(id1, id2); 243 assert_ne!(id1, id3); 244 } 245 246 #[test] 247 fn sub_request_id_ordering() { 248 let id1 = OutboxSubId(1); 249 let id2 = OutboxSubId(2); 250 251 assert!(id1 < id2); 252 } 253 }