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