Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
1use dasl::drisl::Value as DrislValue; 2use tinyjson::JsonValue; 3 4use crate::{parse_any_link, CollectedLink}; 5 6pub fn walk_record(path: &str, v: &JsonValue, found: &mut Vec<CollectedLink>) { 7 match v { 8 JsonValue::Object(o) => { 9 for (key, child) in o { 10 walk_record(&format!("{path}.{key}"), child, found) 11 } 12 } 13 JsonValue::Array(a) => { 14 for child in a { 15 let child_p = match child { 16 JsonValue::Object(o) => { 17 if let Some(JsonValue::String(t)) = o.get("$type") { 18 format!("{path}[{t}]") 19 } else { 20 format!("{path}[]") 21 } 22 } 23 _ => format!("{path}[]"), 24 }; 25 walk_record(&child_p, child, found) 26 } 27 } 28 JsonValue::String(s) => { 29 if let Some(link) = parse_any_link(s) { 30 found.push(CollectedLink { 31 path: path.to_string(), 32 target: link, 33 }); 34 } 35 } 36 _ => {} 37 } 38} 39 40pub fn walk_drisl(path: &str, v: &DrislValue, found: &mut Vec<CollectedLink>) { 41 match v { 42 DrislValue::Map(o) => { 43 for (key, child) in o { 44 walk_drisl(&format!("{path}.{key}"), child, found) 45 } 46 } 47 DrislValue::Array(a) => { 48 for child in a { 49 let child_p = match child { 50 DrislValue::Map(o) => { 51 if let Some(DrislValue::Text(t)) = o.get("$type") { 52 format!("{path}[{t}]") 53 } else { 54 format!("{path}[]") 55 } 56 } 57 _ => format!("{path}[]"), 58 }; 59 walk_drisl(&child_p, child, found) 60 } 61 } 62 DrislValue::Text(s) => { 63 if let Some(link) = parse_any_link(s) { 64 found.push(CollectedLink { 65 path: path.to_string(), 66 target: link, 67 }); 68 } 69 } 70 _ => {} 71 } 72} 73 74pub fn collect_links(v: &JsonValue) -> Vec<CollectedLink> { 75 let mut found = vec![]; 76 walk_record("", v, &mut found); 77 found 78} 79 80pub fn collect_links_drisl(v: &DrislValue) -> Vec<CollectedLink> { 81 let mut found = vec![]; 82 walk_drisl("", v, &mut found); 83 found 84} 85 86#[cfg(test)] 87mod tests { 88 use super::*; 89 use crate::Link; 90 91 fn l(path: &str, target: Link) -> CollectedLink { 92 CollectedLink { 93 path: path.into(), 94 target, 95 } 96 } 97 98 #[test] 99 fn test_collect_links() { 100 let rec = r#"{"a": "https://example.com", "b": "not a link"}"#; 101 let json = collect_links(&rec.parse().unwrap()); 102 assert_eq!(json, vec![l(".a", Link::Uri("https://example.com".into()))]); 103 } 104 105 #[test] 106 fn test_bsky_feed_post_record_reply() { 107 let rec = r#"{ 108 "$type": "app.bsky.feed.post", 109 "createdAt": "2025-01-08T20:52:43.041Z", 110 "langs": [ 111 "en" 112 ], 113 "reply": { 114 "parent": { 115 "cid": "bafyreifk3bwnmulk37ezrarg4ouheqnhgucypynftqafl4limssogvzk6i", 116 "uri": "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f" 117 }, 118 "root": { 119 "cid": "bafyreifk3bwnmulk37ezrarg4ouheqnhgucypynftqafl4limssogvzk6i", 120 "uri": "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f" 121 } 122 }, 123 "text": "Yup!" 124 }"#; 125 let mut json = collect_links(&rec.parse().unwrap()); 126 json.sort_by_key(|c| (c.path.clone(), c.target.clone())); 127 assert_eq!( 128 json, 129 vec![ 130 l( 131 ".reply.parent.uri", 132 Link::AtUri( 133 "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f" 134 .into() 135 ) 136 ), 137 l( 138 ".reply.root.uri", 139 Link::AtUri( 140 "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f" 141 .into() 142 ) 143 ), 144 ] 145 ) 146 } 147 148 #[test] 149 fn test_bsky_feed_post_record_embed() { 150 let rec = r#"{ 151 "$type": "app.bsky.feed.post", 152 "createdAt": "2025-01-08T20:52:39.539Z", 153 "embed": { 154 "$type": "app.bsky.embed.external", 155 "external": { 156 "description": "YouTube video by More Perfect Union", 157 "thumb": { 158 "$type": "blob", 159 "ref": { 160 "$link": "bafkreifxuvkbqksq5usi4cryex37o4absjexuouvgenlb62ojsx443b2tm" 161 }, 162 "mimeType": "image/jpeg", 163 "size": 477460 164 }, 165 "title": "Corporations & Wealthy Elites Are Coopting Our Government. Who Can Stop Them?", 166 "uri": "https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq" 167 } 168 }, 169 "facets": [ 170 { 171 "features": [ 172 { 173 "$type": "app.bsky.richtext.facet#link", 174 "uri": "https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq" 175 } 176 ], 177 "index": { 178 "byteEnd": 24, 179 "byteStart": 0 180 } 181 } 182 ], 183 "langs": [ 184 "en" 185 ], 186 "text": "youtu.be/oKXm4szEP1Q?..." 187 }"#; 188 let mut json = collect_links(&rec.parse().unwrap()); 189 json.sort_by_key(|c| (c.path.clone(), c.target.clone())); 190 assert_eq!( 191 json, 192 vec![ 193 l( 194 ".embed.external.uri", 195 Link::Uri("https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq".into()), 196 ), 197 l( 198 ".facets[].features[app.bsky.richtext.facet#link].uri", 199 Link::Uri("https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq".into()), 200 ), 201 ] 202 ) 203 } 204}