Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm

walk record json and get links

Changed files
+156 -4
src
+19 -4
src/lib.rs
··· 2 2 3 3 pub mod at_uri; 4 4 pub mod did; 5 + pub mod record; 6 + 7 + pub use record::collect_links; 5 8 6 9 #[derive(Debug, PartialEq)] 7 10 pub enum Link { ··· 10 13 Did(String), 11 14 } 12 15 16 + impl 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 + } 25 + 13 26 // normalizing is a bit opinionated but eh 14 27 pub fn parse_uri(s: &str) -> Option<String> { 15 28 Uri::parse(s).map(|u| u.normalize().into_string()).ok() 16 29 } 17 30 18 - pub fn parse_any(s: &str) -> Option<Link> { 31 + pub fn parse_any_link(s: &str) -> Option<Link> { 19 32 at_uri::parse_at_uri(s).map(Link::AtUri).or_else(|| { 20 33 did::parse_did(s) 21 34 .map(Link::Did) ··· 49 62 #[test] 50 63 fn test_any_parse() { 51 64 assert_eq!( 52 - parse_any("https://example.com"), 65 + parse_any_link("https://example.com"), 53 66 Some(Link::Uri("https://example.com".into())) 54 67 ); 55 68 56 69 assert_eq!( 57 - parse_any("at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26"), 70 + parse_any_link( 71 + "at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26" 72 + ), 58 73 Some(Link::AtUri( 59 74 "at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26".into() 60 75 )), 61 76 ); 62 77 63 78 assert_eq!( 64 - parse_any("did:plc:44ybard66vv44zksje25o7dz"), 79 + parse_any_link("did:plc:44ybard66vv44zksje25o7dz"), 65 80 Some(Link::Did("did:plc:44ybard66vv44zksje25o7dz".into())) 66 81 ) 67 82 }
+137
src/record.rs
··· 1 + use tinyjson::JsonValue; 2 + 3 + use crate::parse_any_link; 4 + 5 + pub fn walk_record(path: &str, v: &JsonValue, found: &mut Vec<(String, String)>) { 6 + match v { 7 + JsonValue::Object(o) => { 8 + for (key, child) in o { 9 + walk_record(&format!("{path}.{key}"), child, found) 10 + } 11 + } 12 + JsonValue::Array(a) => { 13 + let p = format!("{path}[]"); 14 + for child in a { 15 + walk_record(&p, child, found) 16 + } 17 + } 18 + JsonValue::String(s) => { 19 + if let Some(link) = parse_any_link(s) { 20 + found.push((path.to_string(), link.into_string())); 21 + } 22 + } 23 + _ => {} 24 + } 25 + } 26 + 27 + pub fn collect_links(v: JsonValue) -> Vec<(String, String)> { 28 + let mut found = vec![]; 29 + walk_record("", &v, &mut found); 30 + found 31 + } 32 + 33 + #[cfg(test)] 34 + mod tests { 35 + use super::*; 36 + 37 + #[test] 38 + fn test_collect_links() { 39 + let rec = r#"{"a": "https://example.com", "b": "not a link"}"#; 40 + let json = collect_links(rec.parse().unwrap()); 41 + assert_eq!(json, vec![(".a".into(), "https://example.com".into())]); 42 + } 43 + 44 + #[test] 45 + fn test_bsky_feed_post_record_reply() { 46 + let rec = r#"{ 47 + "$type": "app.bsky.feed.post", 48 + "createdAt": "2025-01-08T20:52:43.041Z", 49 + "langs": [ 50 + "en" 51 + ], 52 + "reply": { 53 + "parent": { 54 + "cid": "bafyreifk3bwnmulk37ezrarg4ouheqnhgucypynftqafl4limssogvzk6i", 55 + "uri": "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f" 56 + }, 57 + "root": { 58 + "cid": "bafyreifk3bwnmulk37ezrarg4ouheqnhgucypynftqafl4limssogvzk6i", 59 + "uri": "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f" 60 + } 61 + }, 62 + "text": "Yup!" 63 + }"#; 64 + let mut json = collect_links(rec.parse().unwrap()); 65 + json.sort(); 66 + assert_eq!( 67 + json, 68 + vec![ 69 + ( 70 + ".reply.parent.uri".into(), 71 + "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f".into() 72 + ), 73 + ( 74 + ".reply.root.uri".into(), 75 + "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f".into() 76 + ), 77 + ] 78 + ) 79 + } 80 + 81 + #[test] 82 + fn test_bsky_feed_post_record_embed() { 83 + let rec = r#"{ 84 + "$type": "app.bsky.feed.post", 85 + "createdAt": "2025-01-08T20:52:39.539Z", 86 + "embed": { 87 + "$type": "app.bsky.embed.external", 88 + "external": { 89 + "description": "YouTube video by More Perfect Union", 90 + "thumb": { 91 + "$type": "blob", 92 + "ref": { 93 + "$link": "bafkreifxuvkbqksq5usi4cryex37o4absjexuouvgenlb62ojsx443b2tm" 94 + }, 95 + "mimeType": "image/jpeg", 96 + "size": 477460 97 + }, 98 + "title": "Corporations & Wealthy Elites Are Coopting Our Government. Who Can Stop Them?", 99 + "uri": "https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq" 100 + } 101 + }, 102 + "facets": [ 103 + { 104 + "features": [ 105 + { 106 + "$type": "app.bsky.richtext.facet#link", 107 + "uri": "https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq" 108 + } 109 + ], 110 + "index": { 111 + "byteEnd": 24, 112 + "byteStart": 0 113 + } 114 + } 115 + ], 116 + "langs": [ 117 + "en" 118 + ], 119 + "text": "youtu.be/oKXm4szEP1Q?..." 120 + }"#; 121 + let mut json = collect_links(rec.parse().unwrap()); 122 + json.sort(); 123 + assert_eq!( 124 + json, 125 + vec![ 126 + ( 127 + ".embed.external.uri".into(), 128 + "https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq".into() 129 + ), 130 + ( 131 + ".facets[].features[].uri".into(), 132 + "https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq".into() 133 + ), 134 + ] 135 + ) 136 + } 137 + }