/// AT URI utilities for parsing and reconstructing AT URIs /// /// AT URIs have the format: at://did:plc:xyz/collection/rkey /// /// After optimization, we store: /// - actor_id (INTEGER FK to actors.id) /// - collection_id (SMALLINT FK to record_types.id) /// - rkey (TEXT) /// /// These utilities help convert between the formats. /// Parse an AT URI into its components /// /// # Arguments /// * `at_uri` - The full AT URI (e.g., "at://did:plc:xyz/app.bsky.feed.post/abc123") /// /// # Returns /// * `Some((did, collection, rkey))` - The three components /// * `None` - If the URI is malformed pub fn parse_at_uri(at_uri: &str) -> Option<(&str, &str, &str)> { // Expected format: at://did:plc:xyz/app.bsky.feed.post/rkey let without_prefix = at_uri.strip_prefix("at://")?; let mut parts = without_prefix.splitn(3, '/'); let did = parts.next()?; let collection = parts.next()?; let rkey = parts.next()?; // Basic validation if did.is_empty() || collection.is_empty() || rkey.is_empty() { return None; } Some((did, collection, rkey)) } /// Reconstruct an AT URI from its components /// /// # Arguments /// * `did` - The DID (e.g., "did:plc:xyz") /// * `collection` - The collection name (e.g., "app.bsky.feed.post") /// * `rkey` - The record key /// /// # Returns /// * The full AT URI #[inline] pub fn build_at_uri(did: &str, collection: &str, rkey: &str) -> String { format!("at://{}/{}/{}", did, collection, rkey) } /// Extract just the rkey from an AT URI /// /// # Arguments /// * `at_uri` - The full AT URI /// /// # Returns /// * `Some(rkey)` - The record key /// * `None` - If the URI is malformed #[inline] pub fn extract_rkey(at_uri: &str) -> Option<&str> { parse_at_uri(at_uri).map(|(_, _, rkey)| rkey) } /// Extract just the collection from an AT URI /// /// # Arguments /// * `at_uri` - The full AT URI /// /// # Returns /// * `Some(collection)` - The collection name /// * `None` - If the URI is malformed #[inline] pub fn extract_collection(at_uri: &str) -> Option<&str> { parse_at_uri(at_uri).map(|(_, collection, _)| collection) } /// Extract just the DID from an AT URI /// /// # Arguments /// * `at_uri` - The full AT URI /// /// # Returns /// * `Some(did)` - The DID /// * `None` - If the URI is malformed #[inline] pub fn extract_did(at_uri: &str) -> Option<&str> { parse_at_uri(at_uri).map(|(did, _, _)| did) } #[cfg(test)] mod tests { use super::*; const SAMPLE_URI: &str = "at://did:plc:xyz123abc/app.bsky.feed.post/3k2abc123"; #[test] fn test_parse_at_uri() { let (did, collection, rkey) = parse_at_uri(SAMPLE_URI).unwrap(); assert_eq!(did, "did:plc:xyz123abc"); assert_eq!(collection, "app.bsky.feed.post"); assert_eq!(rkey, "3k2abc123"); } #[test] fn test_build_at_uri() { let uri = build_at_uri("did:plc:xyz123abc", "app.bsky.feed.post", "3k2abc123"); assert_eq!(uri, SAMPLE_URI); } #[test] fn test_roundtrip() { let (did, collection, rkey) = parse_at_uri(SAMPLE_URI).unwrap(); let reconstructed = build_at_uri(did, collection, rkey); assert_eq!(reconstructed, SAMPLE_URI); } #[test] fn test_extract_rkey() { assert_eq!(extract_rkey(SAMPLE_URI), Some("3k2abc123")); } #[test] fn test_extract_collection() { assert_eq!(extract_collection(SAMPLE_URI), Some("app.bsky.feed.post")); } #[test] fn test_extract_did() { assert_eq!(extract_did(SAMPLE_URI), Some("did:plc:xyz123abc")); } #[test] fn test_invalid_uris() { assert!(parse_at_uri("not-an-at-uri").is_none()); assert!(parse_at_uri("at://").is_none()); assert!(parse_at_uri("at://did/collection").is_none()); // Missing rkey assert!(parse_at_uri("https://example.com").is_none()); } #[test] fn test_rkey_with_slashes() { // Some rkeys might contain encoded slashes or special chars let uri = "at://did:plc:abc/app.bsky.feed.post/rkey-with-dash"; let (_, _, rkey) = parse_at_uri(uri).unwrap(); assert_eq!(rkey, "rkey-with-dash"); } }