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
···
pub mod at_uri;
pub mod did;
#[derive(Debug, PartialEq)]
pub enum Link {
···
Did(String),
}
// normalizing is a bit opinionated but eh
pub fn parse_uri(s: &str) -> Option<String> {
Uri::parse(s).map(|u| u.normalize().into_string()).ok()
}
-
pub fn parse_any(s: &str) -> Option<Link> {
at_uri::parse_at_uri(s).map(Link::AtUri).or_else(|| {
did::parse_did(s)
.map(Link::Did)
···
#[test]
fn test_any_parse() {
assert_eq!(
-
parse_any("https://example.com"),
Some(Link::Uri("https://example.com".into()))
);
assert_eq!(
-
parse_any("at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26"),
Some(Link::AtUri(
"at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26".into()
)),
);
assert_eq!(
-
parse_any("did:plc:44ybard66vv44zksje25o7dz"),
Some(Link::Did("did:plc:44ybard66vv44zksje25o7dz".into()))
)
}
···
pub mod at_uri;
pub mod did;
+
pub mod record;
+
+
pub use record::collect_links;
#[derive(Debug, PartialEq)]
pub enum Link {
···
Did(String),
}
+
impl Link {
+
pub fn into_string(self) -> String {
+
match self {
+
Link::AtUri(s) => s,
+
Link::Uri(s) => s,
+
Link::Did(s) => s,
+
}
+
}
+
}
+
// normalizing is a bit opinionated but eh
pub fn parse_uri(s: &str) -> Option<String> {
Uri::parse(s).map(|u| u.normalize().into_string()).ok()
}
+
pub fn parse_any_link(s: &str) -> Option<Link> {
at_uri::parse_at_uri(s).map(Link::AtUri).or_else(|| {
did::parse_did(s)
.map(Link::Did)
···
#[test]
fn test_any_parse() {
assert_eq!(
+
parse_any_link("https://example.com"),
Some(Link::Uri("https://example.com".into()))
);
assert_eq!(
+
parse_any_link(
+
"at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26"
+
),
Some(Link::AtUri(
"at://did:plc:44ybard66vv44zksje25o7dz/app.bsky.feed.post/3jwdwj2ctlk26".into()
)),
);
assert_eq!(
+
parse_any_link("did:plc:44ybard66vv44zksje25o7dz"),
Some(Link::Did("did:plc:44ybard66vv44zksje25o7dz".into()))
)
}
+137
src/record.rs
···
···
+
use tinyjson::JsonValue;
+
+
use crate::parse_any_link;
+
+
pub fn walk_record(path: &str, v: &JsonValue, found: &mut Vec<(String, String)>) {
+
match v {
+
JsonValue::Object(o) => {
+
for (key, child) in o {
+
walk_record(&format!("{path}.{key}"), child, found)
+
}
+
}
+
JsonValue::Array(a) => {
+
let p = format!("{path}[]");
+
for child in a {
+
walk_record(&p, child, found)
+
}
+
}
+
JsonValue::String(s) => {
+
if let Some(link) = parse_any_link(s) {
+
found.push((path.to_string(), link.into_string()));
+
}
+
}
+
_ => {}
+
}
+
}
+
+
pub fn collect_links(v: JsonValue) -> Vec<(String, String)> {
+
let mut found = vec![];
+
walk_record("", &v, &mut found);
+
found
+
}
+
+
#[cfg(test)]
+
mod tests {
+
use super::*;
+
+
#[test]
+
fn test_collect_links() {
+
let rec = r#"{"a": "https://example.com", "b": "not a link"}"#;
+
let json = collect_links(rec.parse().unwrap());
+
assert_eq!(json, vec![(".a".into(), "https://example.com".into())]);
+
}
+
+
#[test]
+
fn test_bsky_feed_post_record_reply() {
+
let rec = r#"{
+
"$type": "app.bsky.feed.post",
+
"createdAt": "2025-01-08T20:52:43.041Z",
+
"langs": [
+
"en"
+
],
+
"reply": {
+
"parent": {
+
"cid": "bafyreifk3bwnmulk37ezrarg4ouheqnhgucypynftqafl4limssogvzk6i",
+
"uri": "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f"
+
},
+
"root": {
+
"cid": "bafyreifk3bwnmulk37ezrarg4ouheqnhgucypynftqafl4limssogvzk6i",
+
"uri": "at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f"
+
}
+
},
+
"text": "Yup!"
+
}"#;
+
let mut json = collect_links(rec.parse().unwrap());
+
json.sort();
+
assert_eq!(
+
json,
+
vec![
+
(
+
".reply.parent.uri".into(),
+
"at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f".into()
+
),
+
(
+
".reply.root.uri".into(),
+
"at://did:plc:b3rzzkblqsxhr3dgcueymkqe/app.bsky.feed.post/3lf6yc4drhk2f".into()
+
),
+
]
+
)
+
}
+
+
#[test]
+
fn test_bsky_feed_post_record_embed() {
+
let rec = r#"{
+
"$type": "app.bsky.feed.post",
+
"createdAt": "2025-01-08T20:52:39.539Z",
+
"embed": {
+
"$type": "app.bsky.embed.external",
+
"external": {
+
"description": "YouTube video by More Perfect Union",
+
"thumb": {
+
"$type": "blob",
+
"ref": {
+
"$link": "bafkreifxuvkbqksq5usi4cryex37o4absjexuouvgenlb62ojsx443b2tm"
+
},
+
"mimeType": "image/jpeg",
+
"size": 477460
+
},
+
"title": "Corporations & Wealthy Elites Are Coopting Our Government. Who Can Stop Them?",
+
"uri": "https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq"
+
}
+
},
+
"facets": [
+
{
+
"features": [
+
{
+
"$type": "app.bsky.richtext.facet#link",
+
"uri": "https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq"
+
}
+
],
+
"index": {
+
"byteEnd": 24,
+
"byteStart": 0
+
}
+
}
+
],
+
"langs": [
+
"en"
+
],
+
"text": "youtu.be/oKXm4szEP1Q?..."
+
}"#;
+
let mut json = collect_links(rec.parse().unwrap());
+
json.sort();
+
assert_eq!(
+
json,
+
vec![
+
(
+
".embed.external.uri".into(),
+
"https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq".into()
+
),
+
(
+
".facets[].features[].uri".into(),
+
"https://youtu.be/oKXm4szEP1Q?si=_0n_uPu4qNKokMnq".into()
+
),
+
]
+
)
+
}
+
}