notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }