QuickDID is a high-performance AT Protocol identity resolution service written in Rust. It provides handle-to-DID resolution with Redis-backed caching and queue processing.
at main 150 lines 4.8 kB view raw
1//! Core traits for handle resolution. 2//! 3//! This module defines the fundamental `HandleResolver` trait that all resolver 4//! implementations must satisfy. 5 6use super::errors::HandleResolverError; 7use async_trait::async_trait; 8 9/// Core trait for handle-to-DID resolution. 10/// 11/// Implementations of this trait provide different strategies for resolving 12/// AT Protocol handles (like `alice.bsky.social`) to their corresponding 13/// DID identifiers (like `did:plc:xyz123`). 14/// 15/// # Examples 16/// 17/// ```no_run 18/// use async_trait::async_trait; 19/// use quickdid::handle_resolver::{HandleResolver, HandleResolverError}; 20/// use std::time::{SystemTime, UNIX_EPOCH}; 21/// 22/// struct MyResolver; 23/// 24/// #[async_trait] 25/// impl HandleResolver for MyResolver { 26/// async fn resolve(&self, s: &str) -> Result<(String, u64), HandleResolverError> { 27/// // Custom resolution logic 28/// let did = format!("did:plc:{}", s.replace('.', "")); 29/// let timestamp = SystemTime::now() 30/// .duration_since(UNIX_EPOCH) 31/// .unwrap() 32/// .as_secs(); 33/// Ok((did, timestamp)) 34/// } 35/// } 36/// ``` 37#[async_trait] 38pub trait HandleResolver: Send + Sync { 39 /// Resolve a handle to its DID with timestamp. 40 /// 41 /// # Arguments 42 /// 43 /// * `s` - The handle to resolve (e.g., "alice.bsky.social") 44 /// 45 /// # Returns 46 /// 47 /// A tuple containing: 48 /// - The resolved DID string 49 /// - The resolution timestamp as UNIX epoch seconds 50 /// 51 /// # Errors 52 /// 53 /// Returns [`HandleResolverError`] if: 54 /// - The handle cannot be resolved 55 /// - Network errors occur during resolution 56 /// - The handle is invalid or doesn't exist 57 async fn resolve(&self, s: &str) -> Result<(String, u64), HandleResolverError>; 58 59 /// Purge a handle or DID from the cache. 60 /// 61 /// This method removes cached entries for the given identifier, which can be 62 /// either a handle (e.g., "alice.bsky.social") or a DID (e.g., "did:plc:xyz123"). 63 /// Implementations should handle bidirectional purging where applicable. 64 /// 65 /// # Arguments 66 /// 67 /// * `identifier` - Either a handle or DID to purge from cache 68 /// 69 /// # Returns 70 /// 71 /// Ok(()) if the purge was successful or if the identifier wasn't cached. 72 /// Most implementations will simply return Ok(()) as a no-op. 73 /// 74 /// # Default Implementation 75 /// 76 /// The default implementation is a no-op that always returns Ok(()). 77 async fn purge(&self, _subject: &str) -> Result<(), HandleResolverError> { 78 Ok(()) 79 } 80 81 /// Set a handle-to-DID mapping in the cache. 82 /// 83 /// This method allows manually setting or updating a cached mapping between 84 /// a handle and its corresponding DID. This is useful for pre-populating 85 /// caches or updating stale entries. 86 /// 87 /// # Arguments 88 /// 89 /// * `handle` - The handle to cache (e.g., "alice.bsky.social") 90 /// * `did` - The DID to associate with the handle (e.g., "did:plc:xyz123") 91 /// 92 /// # Returns 93 /// 94 /// Ok(()) if the mapping was successfully set or if the implementation 95 /// doesn't support manual cache updates. Most implementations will simply 96 /// return Ok(()) as a no-op. 97 /// 98 /// # Default Implementation 99 /// 100 /// The default implementation is a no-op that always returns Ok(()). 101 async fn set(&self, _handle: &str, _did: &str) -> Result<(), HandleResolverError> { 102 Ok(()) 103 } 104} 105 106#[cfg(test)] 107mod tests { 108 use super::*; 109 110 // Simple test resolver that doesn't cache anything 111 struct NoOpTestResolver; 112 113 #[async_trait] 114 impl HandleResolver for NoOpTestResolver { 115 async fn resolve(&self, _s: &str) -> Result<(String, u64), HandleResolverError> { 116 Ok(("did:test:123".to_string(), 1234567890)) 117 } 118 // Uses default purge implementation 119 } 120 121 #[tokio::test] 122 async fn test_default_purge_implementation() { 123 let resolver = NoOpTestResolver; 124 125 // Default implementation should always return Ok(()) 126 assert!(resolver.purge("alice.bsky.social").await.is_ok()); 127 assert!(resolver.purge("did:plc:xyz123").await.is_ok()); 128 assert!(resolver.purge("").await.is_ok()); 129 } 130 131 #[tokio::test] 132 async fn test_default_set_implementation() { 133 let resolver = NoOpTestResolver; 134 135 // Default implementation should always return Ok(()) 136 assert!( 137 resolver 138 .set("alice.bsky.social", "did:plc:xyz123") 139 .await 140 .is_ok() 141 ); 142 assert!( 143 resolver 144 .set("bob.example.com", "did:web:example.com") 145 .await 146 .is_ok() 147 ); 148 assert!(resolver.set("", "").await.is_ok()); 149 } 150}