forked from
smokesignal.events/quickdid
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.
1//! Base handle resolver implementation.
2//!
3//! This module provides the fundamental handle resolution implementation that
4//! performs actual DNS and HTTP lookups to resolve AT Protocol handles to DIDs.
5
6use super::errors::HandleResolverError;
7use super::traits::HandleResolver;
8use crate::metrics::SharedMetricsPublisher;
9use async_trait::async_trait;
10use atproto_identity::resolve::{DnsResolver, resolve_subject};
11use reqwest::Client;
12use std::sync::Arc;
13use std::time::{SystemTime, UNIX_EPOCH};
14
15/// Base handle resolver that performs actual resolution via DNS and HTTP.
16///
17/// This resolver implements the core AT Protocol handle resolution logic:
18/// 1. DNS TXT record lookup for `_atproto.{handle}`
19/// 2. HTTP well-known endpoint query at `https://{handle}/.well-known/atproto-did`
20/// 3. DID document retrieval from PLC directory or web DIDs
21///
22/// # Example
23///
24/// ```no_run
25/// use std::sync::Arc;
26/// use reqwest::Client;
27/// use atproto_identity::resolve::HickoryDnsResolver;
28/// use quickdid::handle_resolver::{create_base_resolver, HandleResolver};
29/// use quickdid::metrics::NoOpMetricsPublisher;
30///
31/// # async fn example() {
32/// let dns_resolver = Arc::new(HickoryDnsResolver::create_resolver(&[]));
33/// let http_client = Client::new();
34/// let metrics = Arc::new(NoOpMetricsPublisher);
35///
36/// let resolver = create_base_resolver(
37/// dns_resolver,
38/// http_client,
39/// metrics,
40/// );
41///
42/// let (did, timestamp) = resolver.resolve("alice.bsky.social").await.unwrap();
43/// println!("Resolved {} at {}", did, timestamp);
44/// # }
45/// ```
46pub(super) struct BaseHandleResolver {
47 /// DNS resolver for handle-to-DID resolution via TXT records.
48 dns_resolver: Arc<dyn DnsResolver>,
49
50 /// HTTP client for DID document retrieval and well-known endpoint queries.
51 http_client: Client,
52
53 /// Metrics publisher for telemetry.
54 metrics: SharedMetricsPublisher,
55}
56
57#[async_trait]
58impl HandleResolver for BaseHandleResolver {
59 async fn resolve(&self, s: &str) -> Result<(String, u64), HandleResolverError> {
60 let start_time = std::time::Instant::now();
61
62 // Perform DNS/HTTP resolution
63 let result = resolve_subject(&self.http_client, &*self.dns_resolver, s)
64 .await
65 .map_err(|e| HandleResolverError::ResolutionFailed(e.to_string()));
66
67 let duration_ms = start_time.elapsed().as_millis() as u64;
68
69 // Publish metrics
70
71 match result {
72 Ok(did) => {
73 self.metrics
74 .time_with_tags(
75 "resolver.base.duration_ms",
76 duration_ms,
77 &[("success", "1")],
78 )
79 .await;
80
81 let timestamp = SystemTime::now()
82 .duration_since(UNIX_EPOCH)
83 .map_err(|e| {
84 HandleResolverError::ResolutionFailed(format!("System time error: {}", e))
85 })?
86 .as_secs();
87
88 Ok((did, timestamp))
89 }
90 Err(e) => {
91 self.metrics
92 .time_with_tags(
93 "resolver.base.duration_ms",
94 duration_ms,
95 &[("success", "0")],
96 )
97 .await;
98 Err(e)
99 }
100 }
101 }
102}
103
104/// Create a new base handle resolver.
105///
106/// This factory function creates a resolver that performs actual DNS and HTTP
107/// lookups for handle resolution.
108///
109/// # Arguments
110///
111/// * `dns_resolver` - DNS resolver for TXT record lookups
112/// * `http_client` - HTTP client for well-known endpoint queries
113/// * `metrics` - Metrics publisher for telemetry
114pub fn create_base_resolver(
115 dns_resolver: Arc<dyn DnsResolver>,
116 http_client: Client,
117 metrics: SharedMetricsPublisher,
118) -> Arc<dyn HandleResolver> {
119 Arc::new(BaseHandleResolver {
120 dns_resolver,
121 http_client,
122 metrics,
123 })
124}