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