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.
···179179 async fn set(&self, handle: &str, did: &str) -> Result<(), HandleResolverError> {
180180 // Normalize the handle to lowercase
181181 let handle = handle.to_lowercase();
182182-182182+183183 // Update the in-memory cache
184184 {
185185 let mut cache = self.cache.write().await;
···190190 );
191191 self.metrics.incr("resolver.memory.set").await;
192192 tracing::debug!("Set handle {} -> DID {} in memory cache", handle, did);
193193-193193+194194 // Track cache size
195195 let cache_size = cache.len() as u64;
196196 self.metrics
197197 .gauge("resolver.memory.cache_entries", cache_size)
198198 .await;
199199 }
200200-200200+201201 // Chain to inner resolver
202202 self.inner.set(&handle, did).await
203203 }
+124-63
src/handle_resolver/redis.rs
···99use crate::handle_resolution_result::HandleResolutionResult;
1010use crate::metrics::SharedMetricsPublisher;
1111use async_trait::async_trait;
1212-use atproto_identity::resolve::{parse_input, InputType};
1212+use atproto_identity::resolve::{InputType, parse_input};
1313use deadpool_redis::{Pool as RedisPool, redis::AsyncCommands};
1414use metrohash::MetroHash64;
1515use std::hash::Hasher as _;
···124124 /// This method removes both the handle->DID mapping and the reverse DID->handle mapping.
125125 async fn purge_handle(&self, handle: &str) -> Result<(), HandleResolverError> {
126126 let handle_key = self.make_key(handle);
127127-127127+128128 match self.pool.get().await {
129129 Ok(mut conn) => {
130130 // First, try to get the cached result to find the associated DID
···142142 if let Ok(cached_result) = HandleResolutionResult::from_bytes(&cached_bytes) {
143143 if let Some(did) = cached_result.to_did() {
144144 let did_key = self.make_key(&did);
145145-145145+146146 // Delete both the handle key and the DID key
147147 let _: Result<(), _> = conn.del(&[&handle_key, &did_key]).await;
148148-148148+149149 tracing::debug!("Purged handle {} and associated DID {}", handle, did);
150150- self.metrics.incr("resolver.redis.purge_handle_success").await;
150150+ self.metrics
151151+ .incr("resolver.redis.purge_handle_success")
152152+ .await;
151153 } else {
152154 // Just delete the handle key if no DID was resolved
153155 let _: Result<(), _> = conn.del(&handle_key).await;
154156 tracing::debug!("Purged unresolved handle {}", handle);
155155- self.metrics.incr("resolver.redis.purge_handle_unresolved").await;
157157+ self.metrics
158158+ .incr("resolver.redis.purge_handle_unresolved")
159159+ .await;
156160 }
157161 } else {
158162 // If we can't deserialize, just delete the handle key
159163 let _: Result<(), _> = conn.del(&handle_key).await;
160164 tracing::warn!("Purged handle {} with undeserializable data", handle);
161161- self.metrics.incr("resolver.redis.purge_handle_corrupt").await;
165165+ self.metrics
166166+ .incr("resolver.redis.purge_handle_corrupt")
167167+ .await;
162168 }
163169 } else {
164170 tracing::debug!("Handle {} not found in cache for purging", handle);
165165- self.metrics.incr("resolver.redis.purge_handle_not_found").await;
171171+ self.metrics
172172+ .incr("resolver.redis.purge_handle_not_found")
173173+ .await;
166174 }
167167-175175+168176 Ok(())
169177 }
170178 Err(e) => {
171179 tracing::warn!("Failed to get Redis connection for purging: {}", e);
172172- self.metrics.incr("resolver.redis.purge_connection_error").await;
173173- Err(HandleResolverError::ResolutionFailed(format!("Redis connection error: {}", e)))
180180+ self.metrics
181181+ .incr("resolver.redis.purge_connection_error")
182182+ .await;
183183+ Err(HandleResolverError::ResolutionFailed(format!(
184184+ "Redis connection error: {}",
185185+ e
186186+ )))
174187 }
175188 }
176189 }
···180193 /// This method removes both the DID->handle mapping and the handle->DID mapping.
181194 async fn purge_did(&self, did: &str) -> Result<(), HandleResolverError> {
182195 let did_key = self.make_key(did);
183183-196196+184197 match self.pool.get().await {
185198 Ok(mut conn) => {
186199 // First, try to get the associated handle from the reverse mapping
···197210 if let Some(handle_bytes) = handle_bytes {
198211 if let Ok(handle) = String::from_utf8(handle_bytes) {
199212 let handle_key = self.make_key(&handle);
200200-213213+201214 // Delete both the DID key and the handle key
202215 let _: Result<(), _> = conn.del(&[&did_key, &handle_key]).await;
203203-216216+204217 tracing::debug!("Purged DID {} and associated handle {}", did, handle);
205218 self.metrics.incr("resolver.redis.purge_did_success").await;
206219 } else {
···211224 }
212225 } else {
213226 tracing::debug!("DID {} not found in cache for purging", did);
214214- self.metrics.incr("resolver.redis.purge_did_not_found").await;
227227+ self.metrics
228228+ .incr("resolver.redis.purge_did_not_found")
229229+ .await;
215230 }
216216-231231+217232 Ok(())
218233 }
219234 Err(e) => {
220235 tracing::warn!("Failed to get Redis connection for purging: {}", e);
221221- self.metrics.incr("resolver.redis.purge_connection_error").await;
222222- Err(HandleResolverError::ResolutionFailed(format!("Redis connection error: {}", e)))
236236+ self.metrics
237237+ .incr("resolver.redis.purge_connection_error")
238238+ .await;
239239+ Err(HandleResolverError::ResolutionFailed(format!(
240240+ "Redis connection error: {}",
241241+ e
242242+ )))
223243 }
224244 }
225245 }
···323343 self.metrics.incr("resolver.redis.cache_set_error").await;
324344 } else {
325345 self.metrics.incr("resolver.redis.cache_set").await;
326326-346346+327347 // For successful resolutions, also store reverse DID -> handle mapping
328348 if let Ok((did, _)) = &result {
329349 let did_key = self.make_key(did);
330350 if let Err(e) = conn
331331- .set_ex::<_, _, ()>(&did_key, handle.as_bytes(), self.ttl_seconds())
351351+ .set_ex::<_, _, ()>(
352352+ &did_key,
353353+ handle.as_bytes(),
354354+ self.ttl_seconds(),
355355+ )
332356 .await
333357 {
334334- tracing::warn!("Failed to cache reverse DID->handle mapping in Redis: {}", e);
335335- self.metrics.incr("resolver.redis.reverse_cache_set_error").await;
358358+ tracing::warn!(
359359+ "Failed to cache reverse DID->handle mapping in Redis: {}",
360360+ e
361361+ );
362362+ self.metrics
363363+ .incr("resolver.redis.reverse_cache_set_error")
364364+ .await;
336365 } else {
337337- tracing::debug!("Cached reverse mapping for DID {}: {}", did, handle);
366366+ tracing::debug!(
367367+ "Cached reverse mapping for DID {}: {}",
368368+ did,
369369+ handle
370370+ );
338371 self.metrics.incr("resolver.redis.reverse_cache_set").await;
339372 }
340373 }
···366399367400 async fn purge(&self, subject: &str) -> Result<(), HandleResolverError> {
368401 // Use atproto_identity's parse_input to properly identify the input type
369369- let parsed_input = parse_input(subject).map_err(|_| HandleResolverError::InvalidSubject(subject.to_string()))?;
402402+ let parsed_input = parse_input(subject)
403403+ .map_err(|_| HandleResolverError::InvalidSubject(subject.to_string()))?;
370404 match parsed_input {
371405 InputType::Handle(handle) => {
372406 // It's a handle, purge using the lowercase version
···391425 let resolution_result = match HandleResolutionResult::success(did) {
392426 Ok(res) => res,
393427 Err(e) => {
394394- tracing::warn!("Failed to create resolution result for set operation: {}", e);
395395- self.metrics.incr("resolver.redis.set_result_create_error").await;
396396- return Err(HandleResolverError::InvalidSubject(
397397- format!("Failed to create resolution result: {}", e)
398398- ));
428428+ tracing::warn!(
429429+ "Failed to create resolution result for set operation: {}",
430430+ e
431431+ );
432432+ self.metrics
433433+ .incr("resolver.redis.set_result_create_error")
434434+ .await;
435435+ return Err(HandleResolverError::InvalidSubject(format!(
436436+ "Failed to create resolution result: {}",
437437+ e
438438+ )));
399439 }
400440 };
401441···409449 {
410450 tracing::warn!("Failed to set handle->DID mapping in Redis: {}", e);
411451 self.metrics.incr("resolver.redis.set_cache_error").await;
412412- return Err(HandleResolverError::ResolutionFailed(
413413- format!("Failed to set cache: {}", e)
414414- ));
452452+ return Err(HandleResolverError::ResolutionFailed(format!(
453453+ "Failed to set cache: {}",
454454+ e
455455+ )));
415456 }
416457417458 // Set the reverse DID -> handle mapping
···420461 .await
421462 {
422463 tracing::warn!("Failed to set DID->handle mapping in Redis: {}", e);
423423- self.metrics.incr("resolver.redis.set_reverse_cache_error").await;
464464+ self.metrics
465465+ .incr("resolver.redis.set_reverse_cache_error")
466466+ .await;
424467 // Don't fail the operation, but log the warning
425468 }
426469···429472 Ok(())
430473 }
431474 Err(e) => {
432432- tracing::warn!("Failed to serialize resolution result for set operation: {}", e);
433433- self.metrics.incr("resolver.redis.set_serialize_error").await;
434434- Err(HandleResolverError::InvalidSubject(
435435- format!("Failed to serialize: {}", e)
436436- ))
475475+ tracing::warn!(
476476+ "Failed to serialize resolution result for set operation: {}",
477477+ e
478478+ );
479479+ self.metrics
480480+ .incr("resolver.redis.set_serialize_error")
481481+ .await;
482482+ Err(HandleResolverError::InvalidSubject(format!(
483483+ "Failed to serialize: {}",
484484+ e
485485+ )))
437486 }
438487 }
439488 }
440489 Err(e) => {
441490 tracing::warn!("Failed to get Redis connection for set operation: {}", e);
442442- self.metrics.incr("resolver.redis.set_connection_error").await;
443443- Err(HandleResolverError::ResolutionFailed(
444444- format!("Redis connection error: {}", e)
445445- ))
491491+ self.metrics
492492+ .incr("resolver.redis.set_connection_error")
493493+ .await;
494494+ Err(HandleResolverError::ResolutionFailed(format!(
495495+ "Redis connection error: {}",
496496+ e
497497+ )))
446498 }
447499 }
448500 }
···634686 let mut h = MetroHash64::default();
635687 h.write(test_handle.as_bytes());
636688 let handle_key = format!("{}{}", test_prefix, h.finish());
637637-689689+638690 let mut h2 = MetroHash64::default();
639691 h2.write(expected_did.as_bytes());
640692 let did_key = format!("{}{}", test_prefix, h2.finish());
641641-693693+642694 // Check handle -> DID mapping exists
643695 let handle_exists: bool = conn.exists(&handle_key).await.unwrap();
644696 assert!(handle_exists, "Handle key should exist in cache");
645645-697697+646698 // Check DID -> handle mapping exists
647699 let did_exists: bool = conn.exists(&did_key).await.unwrap();
648700 assert!(did_exists, "DID key should exist in cache");
649649-701701+650702 // Test purge by handle using the trait method
651703 redis_resolver.purge(test_handle).await.unwrap();
652652-704704+653705 // Verify both keys were deleted
654706 let handle_exists_after: bool = conn.exists(&handle_key).await.unwrap();
655655- assert!(!handle_exists_after, "Handle key should be deleted after purge");
656656-707707+ assert!(
708708+ !handle_exists_after,
709709+ "Handle key should be deleted after purge"
710710+ );
711711+657712 let did_exists_after: bool = conn.exists(&did_key).await.unwrap();
658713 assert!(!did_exists_after, "DID key should be deleted after purge");
659714 }
···664719665720 // Test purge by DID using the trait method
666721 redis_resolver.purge(expected_did).await.unwrap();
667667-722722+668723 // Verify both keys were deleted again
669724 if let Ok(mut conn) = pool.get().await {
670725 let mut h = MetroHash64::default();
671726 h.write(test_handle.as_bytes());
672727 let handle_key = format!("{}{}", test_prefix, h.finish());
673673-728728+674729 let mut h2 = MetroHash64::default();
675730 h2.write(expected_did.as_bytes());
676731 let did_key = format!("{}{}", test_prefix, h2.finish());
677677-732732+678733 let handle_exists: bool = conn.exists(&handle_key).await.unwrap();
679679- assert!(!handle_exists, "Handle key should be deleted after DID purge");
680680-734734+ assert!(
735735+ !handle_exists,
736736+ "Handle key should be deleted after DID purge"
737737+ );
738738+681739 let did_exists: bool = conn.exists(&did_key).await.unwrap();
682740 assert!(!did_exists, "DID key should be deleted after DID purge");
683741 }
···717775718776 // Test different input formats
719777 let test_cases = vec![
720720- ("alice.bsky.social", "alice.bsky.social"), // Handle
721721- ("ALICE.BSKY.SOCIAL", "alice.bsky.social"), // Handle (uppercase)
722722- ("did:plc:abc123", "did:plc:abc123"), // PLC DID
778778+ ("alice.bsky.social", "alice.bsky.social"), // Handle
779779+ ("ALICE.BSKY.SOCIAL", "alice.bsky.social"), // Handle (uppercase)
780780+ ("did:plc:abc123", "did:plc:abc123"), // PLC DID
723781 ("did:web:example.com", "did:web:example.com"), // Web DID
724782 ];
725783···738796 let mut h = MetroHash64::default();
739797 h.write(expected_key.as_bytes());
740798 let key = format!("{}{}", test_prefix, h.finish());
741741-799799+742800 // After purge, key should not exist
743801 let exists: bool = conn.exists(&key).await.unwrap_or(false);
744802 assert!(!exists, "Key for {} should not exist after purge", input);
···789847 assert_eq!(resolved_did, test_did);
790848791849 // Test that uppercase handles are normalized
792792- redis_resolver.set("DAVE.BSKY.SOCIAL", "did:plc:dave456").await.unwrap();
850850+ redis_resolver
851851+ .set("DAVE.BSKY.SOCIAL", "did:plc:dave456")
852852+ .await
853853+ .unwrap();
793854 let (resolved_did2, _) = redis_resolver.resolve("dave.bsky.social").await.unwrap();
794855 assert_eq!(resolved_did2, "did:plc:dave456");
795856···798859 let mut h = MetroHash64::default();
799860 h.write(test_handle.as_bytes());
800861 let handle_key = format!("{}{}", test_prefix, h.finish());
801801-862862+802863 let mut h2 = MetroHash64::default();
803864 h2.write(test_did.as_bytes());
804865 let did_key = format!("{}{}", test_prefix, h2.finish());
805805-866866+806867 // Check both keys exist
807868 let handle_exists: bool = conn.exists(&handle_key).await.unwrap();
808869 assert!(handle_exists, "Handle key should exist after set");
809809-870870+810871 let did_exists: bool = conn.exists(&did_key).await.unwrap();
811872 assert!(did_exists, "DID key should exist after set");
812812-873873+813874 // Clean up test data
814875 let _: Result<(), _> = conn.del(&[&handle_key, &did_key]).await;
815876 }
+21-9
src/handle_resolver/sqlite.rs
···259259 async fn set(&self, handle: &str, did: &str) -> Result<(), HandleResolverError> {
260260 // Normalize the handle to lowercase
261261 let handle = handle.to_lowercase();
262262-262262+263263 // Update the SQLite cache
264264 if let Ok(mut conn) = self.pool.acquire().await {
265265 // Create a resolution result for the successful mapping
266266 let resolution_result = match HandleResolutionResult::success(did) {
267267 Ok(res) => res,
268268 Err(e) => {
269269- tracing::warn!("Failed to create resolution result for set operation: {}", e);
270270- self.metrics.incr("resolver.sqlite.set_result_create_error").await;
269269+ tracing::warn!(
270270+ "Failed to create resolution result for set operation: {}",
271271+ e
272272+ );
273273+ self.metrics
274274+ .incr("resolver.sqlite.set_result_create_error")
275275+ .await;
271276 // Still chain to inner resolver even if we can't cache
272277 return self.inner.set(&handle, did).await;
273278 }
···281286 .duration_since(std::time::UNIX_EPOCH)
282287 .unwrap_or_default()
283288 .as_secs() as i64;
284284-289289+285290 let expires_at = timestamp + self.ttl_seconds as i64;
286286-291291+287292 match sqlx::query(
288293 "INSERT OR REPLACE INTO handle_resolution_cache (handle, resolved_value, created_at, expires_at) VALUES (?, ?, ?, ?)"
289294 )
···306311 }
307312 }
308313 Err(e) => {
309309- tracing::warn!("Failed to serialize resolution result for set operation: {}", e);
310310- self.metrics.incr("resolver.sqlite.set_serialize_error").await;
314314+ tracing::warn!(
315315+ "Failed to serialize resolution result for set operation: {}",
316316+ e
317317+ );
318318+ self.metrics
319319+ .incr("resolver.sqlite.set_serialize_error")
320320+ .await;
311321 // Still chain to inner resolver even if serialization fails
312322 }
313323 }
314324 } else {
315325 tracing::warn!("Failed to get SQLite connection for set operation");
316316- self.metrics.incr("resolver.sqlite.set_connection_error").await;
326326+ self.metrics
327327+ .incr("resolver.sqlite.set_connection_error")
328328+ .await;
317329 }
318318-330330+319331 // Chain to inner resolver
320332 self.inner.set(&handle, did).await
321333 }