Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
1use fluent_uri::Uri; 2use serde::{Deserialize, Serialize}; 3 4pub mod at_uri; 5pub mod did; 6pub mod record; 7 8pub use record::collect_links; 9 10#[derive(Debug, Clone, Ord, Eq, PartialOrd, PartialEq, Serialize, Deserialize)] 11pub enum Link { 12 AtUri(String), 13 Uri(String), 14 Did(String), 15} 16 17impl Link { 18 pub fn into_string(self) -> String { 19 match self { 20 Link::AtUri(s) => s, 21 Link::Uri(s) => s, 22 Link::Did(s) => s, 23 } 24 } 25 pub fn as_str(&self) -> &str { 26 match self { 27 Link::AtUri(s) => s, 28 Link::Uri(s) => s, 29 Link::Did(s) => s, 30 } 31 } 32 pub fn name(&self) -> &'static str { 33 match self { 34 Link::AtUri(_) => "at-uri", 35 Link::Uri(_) => "uri", 36 Link::Did(_) => "did", 37 } 38 } 39 pub fn at_uri_collection(&self) -> Option<String> { 40 if let Link::AtUri(at_uri) = self { 41 at_uri::at_uri_collection(at_uri) 42 } else { 43 None 44 } 45 } 46 pub fn did(&self) -> Option<String> { 47 let did = match self { 48 Link::AtUri(s) => { 49 let rest = s.strip_prefix("at://")?; // todo: this might be safe to unwrap? 50 if let Some((did, _)) = rest.split_once("/") { 51 did 52 } else { 53 rest 54 } 55 } 56 Link::Uri(_) => return None, 57 Link::Did(did) => did, 58 }; 59 Some(did.to_string()) 60 } 61} 62 63#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 64pub struct CollectedLink { 65 pub path: String, 66 pub target: Link, 67} 68 69// normalizing is a bit opinionated but eh 70pub fn parse_uri(s: &str) -> Option<String> { 71 Uri::parse(s).map(|u| u.normalize().into_string()).ok() 72} 73 74pub fn parse_any_link(s: &str) -> Option<Link> { 75 at_uri::parse_at_uri(s).map(Link::AtUri).or_else(|| { 76 did::parse_did(s) 77 .map(Link::Did) 78 .or_else(|| parse_uri(s).map(Link::Uri)) 79 }) 80} 81 82#[cfg(test)] 83mod tests { 84 use super::*; 85 86 #[test] 87 fn test_uri_parse() { 88 let s = "https://example.com"; 89 let uri = parse_uri(s).unwrap(); 90 assert_eq!(uri.as_str(), s); 91 } 92 93 #[test] 94 fn test_uri_normalizes() { 95 let s = "HTTPS://example.com/../"; 96 let uri = parse_uri(s).unwrap(); 97 assert_eq!(uri.as_str(), "https://example.com/"); 98 } 99 100 #[test] 101 fn test_uri_invalid() { 102 assert!(parse_uri("https:\\bad-example.com").is_none()); 103 } 104 105 #[test] 106 fn test_any_parse() { 107 assert_eq!( 108 parse_any_link("https://example.com"), 109 Some(Link::Uri("https://example.com".into())) 110 ); 111 112 assert_eq!( 113 parse_any_link( 114 "at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26" 115 ), 116 Some(Link::AtUri( 117 "at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26".into() 118 )), 119 ); 120 121 assert_eq!( 122 parse_any_link("did:plc:44ybard66vv44zksje25o7dz"), 123 Some(Link::Did("did:plc:44ybard66vv44zksje25o7dz".into())) 124 ) 125 } 126 127 #[test] 128 fn test_at_uri_collection() { 129 assert_eq!( 130 parse_any_link("https://example.com") 131 .unwrap() 132 .at_uri_collection(), 133 None 134 ); 135 assert_eq!( 136 parse_any_link("did:web:bad-example.com") 137 .unwrap() 138 .at_uri_collection(), 139 None 140 ); 141 assert_eq!( 142 parse_any_link("at://did:web:bad-example.com/my.collection/3jwdwj2ctlk26") 143 .unwrap() 144 .at_uri_collection(), 145 Some("my.collection".into()) 146 ); 147 } 148}