Monorepo for Tangled tangled.org
6

Configure Feed

Select the types of activity you want to include in your feed.

at master 6.2 kB View raw
1use std::collections::{HashMap, HashSet}; 2use std::sync::Mutex; 3 4use bobbin_types::knot_acl::KnotHostKey; 5use jacquard_common::DefaultStr; 6use jacquard_common::types::did::Did; 7use jacquard_common::types::string::AtUri; 8 9#[derive(Default)] 10struct Inner { 11 hosts: HashSet<KnotHostKey>, 12 repos: HashMap<KnotHostKey, HashSet<Did<DefaultStr>>>, 13 repo_host: HashMap<Did<DefaultStr>, KnotHostKey>, 14 legacy_members: HashMap<AtUri<DefaultStr>, KnotHostKey>, 15} 16 17#[derive(Default)] 18pub struct KnotRegistry { 19 inner: Mutex<Inner>, 20} 21 22impl KnotRegistry { 23 pub fn new() -> Self { 24 Self::default() 25 } 26 27 pub fn observe_host(&self, host: &KnotHostKey) { 28 self.inner.lock().unwrap().hosts.insert(host.clone()); 29 } 30 31 pub fn observe_repo(&self, host: &KnotHostKey, repo: Did<DefaultStr>) { 32 let mut inner = self.inner.lock().unwrap(); 33 inner.hosts.insert(host.clone()); 34 inner 35 .repos 36 .entry(host.clone()) 37 .or_default() 38 .insert(repo.clone()); 39 inner.repo_host.insert(repo, host.clone()); 40 } 41 42 pub fn hosts(&self) -> Vec<KnotHostKey> { 43 self.inner.lock().unwrap().hosts.iter().cloned().collect() 44 } 45 46 pub fn repos(&self, host: &KnotHostKey) -> Vec<Did<DefaultStr>> { 47 self.inner 48 .lock() 49 .unwrap() 50 .repos 51 .get(host) 52 .map(|set| set.iter().cloned().collect()) 53 .unwrap_or_default() 54 } 55 56 pub fn repo_on_host(&self, host: &KnotHostKey, repo: &Did<DefaultStr>) -> bool { 57 self.inner 58 .lock() 59 .unwrap() 60 .repos 61 .get(host) 62 .is_some_and(|set| set.contains(repo)) 63 } 64 65 pub fn host_of_repo(&self, repo: &Did<DefaultStr>) -> Option<KnotHostKey> { 66 self.inner.lock().unwrap().repo_host.get(repo).cloned() 67 } 68 69 pub fn note_legacy_member(&self, source: AtUri<DefaultStr>, host: &KnotHostKey) { 70 self.inner 71 .lock() 72 .unwrap() 73 .legacy_members 74 .insert(source, host.clone()); 75 } 76 77 pub fn forget_legacy_member(&self, source: &AtUri<DefaultStr>) { 78 self.inner.lock().unwrap().legacy_members.remove(source); 79 } 80 81 pub fn drain_legacy_members(&self, host: &KnotHostKey) -> Vec<AtUri<DefaultStr>> { 82 let mut inner = self.inner.lock().unwrap(); 83 let matched: Vec<AtUri<DefaultStr>> = inner 84 .legacy_members 85 .iter() 86 .filter(|(_, member_host)| member_host.as_str() == host.as_str()) 87 .map(|(source, _)| source.clone()) 88 .collect(); 89 matched.iter().for_each(|source| { 90 inner.legacy_members.remove(source); 91 }); 92 matched 93 } 94} 95 96#[cfg(test)] 97mod tests { 98 use super::*; 99 100 fn did(s: &str) -> Did<DefaultStr> { 101 Did::new_owned(s).unwrap() 102 } 103 104 fn at(s: &str) -> AtUri<DefaultStr> { 105 AtUri::new_owned(s).unwrap() 106 } 107 108 fn host(s: &str) -> KnotHostKey { 109 KnotHostKey::new(s) 110 } 111 112 #[test] 113 fn observe_repo_registers_host_and_repo() { 114 let registry = KnotRegistry::new(); 115 registry.observe_repo(&host("oyster.cafe"), did("did:plc:scallop")); 116 registry.observe_repo(&host("oyster.cafe"), did("did:plc:limpet")); 117 118 assert_eq!(registry.hosts(), vec![host("oyster.cafe")]); 119 let mut repos = registry.repos(&host("oyster.cafe")); 120 repos.sort_by(|a, b| a.as_ref().cmp(b.as_ref())); 121 assert_eq!(repos, vec![did("did:plc:limpet"), did("did:plc:scallop")]); 122 } 123 124 #[test] 125 fn observe_repo_dedups() { 126 let registry = KnotRegistry::new(); 127 registry.observe_repo(&host("nel.pet"), did("did:plc:whelk")); 128 registry.observe_repo(&host("nel.pet"), did("did:plc:whelk")); 129 assert_eq!(registry.repos(&host("nel.pet")), vec![did("did:plc:whelk")]); 130 } 131 132 #[test] 133 fn observe_host_without_repos() { 134 let registry = KnotRegistry::new(); 135 registry.observe_host(&host("oyster.cafe")); 136 assert_eq!(registry.hosts(), vec![host("oyster.cafe")]); 137 assert!(registry.repos(&host("oyster.cafe")).is_empty()); 138 } 139 140 #[test] 141 fn host_lookups_are_case_insensitive() { 142 let registry = KnotRegistry::new(); 143 registry.observe_repo(&host("KT.Oyster.Cafe"), did("did:plc:scallop")); 144 assert!(registry.repo_on_host(&host("kt.oyster.cafe"), &did("did:plc:scallop"))); 145 assert_eq!( 146 registry.host_of_repo(&did("did:plc:scallop")), 147 Some(host("kt.oyster.cafe")) 148 ); 149 } 150 151 #[test] 152 fn host_of_repo_resolves_owning_knot() { 153 let registry = KnotRegistry::new(); 154 registry.observe_repo(&host("oyster.cafe"), did("did:plc:scallop")); 155 assert_eq!( 156 registry.host_of_repo(&did("did:plc:scallop")), 157 Some(host("oyster.cafe")) 158 ); 159 assert_eq!(registry.host_of_repo(&did("did:plc:limpet")), None); 160 } 161 162 #[test] 163 fn drain_legacy_members_returns_only_matching_host() { 164 let registry = KnotRegistry::new(); 165 let here = at("at://did:plc:akshay/sh.tangled.knot.member/r1"); 166 let elsewhere = at("at://did:plc:akshay/sh.tangled.knot.member/r2"); 167 registry.note_legacy_member(here.clone(), &host("oyster.cafe")); 168 registry.note_legacy_member(elsewhere.clone(), &host("nel.pet")); 169 170 assert_eq!( 171 registry.drain_legacy_members(&host("oyster.cafe")), 172 vec![here] 173 ); 174 assert!( 175 registry 176 .drain_legacy_members(&host("oyster.cafe")) 177 .is_empty() 178 ); 179 assert_eq!( 180 registry.drain_legacy_members(&host("nel.pet")), 181 vec![elsewhere] 182 ); 183 } 184 185 #[test] 186 fn forget_legacy_member_drops_source() { 187 let registry = KnotRegistry::new(); 188 let source = at("at://did:plc:akshay/sh.tangled.knot.member/r1"); 189 registry.note_legacy_member(source.clone(), &host("oyster.cafe")); 190 registry.forget_legacy_member(&source); 191 assert!( 192 registry 193 .drain_legacy_members(&host("oyster.cafe")) 194 .is_empty() 195 ); 196 } 197}