forked from
microcosm.blue/microcosm-rs
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}