don't
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(identity): eliminate async_trait dependency

Signed-off-by: tjh <did:plc:65gha4t3avpfpzmvpbwovss7>

tjh.dev da42889e c802fc84

verified
+146 -74
+1 -1
Cargo.lock
··· 2333 2333 name = "identity" 2334 2334 version = "0.0.0" 2335 2335 dependencies = [ 2336 - "async-trait", 2337 2336 "atproto", 2337 + "futures-util", 2338 2338 "hickory-resolver", 2339 2339 "moka", 2340 2340 "reqwest",
+1 -1
crates/identity/Cargo.toml
··· 17 17 tracing.workspace = true 18 18 url.workspace = true 19 19 20 - async-trait = "0.1.89" 20 + futures-util = "0.3.31" 21 21 hickory-resolver = "0.25.2" 22 22 moka = "0.12.11" 23 23 tokio = { version = "1.47.1", default-features = false, features = ["macros"] }
+74 -33
crates/identity/src/lib.rs
··· 1 1 mod document; 2 2 mod resolvers; 3 3 4 + use core::fmt; 5 + 6 + use futures_util::{FutureExt as _, future::BoxFuture}; 7 + 4 8 pub use atproto::did::Did; 5 9 pub use document::{DidDocument, Service, VerificationMethod}; 6 10 ··· 13 9 pub type HttpClient = reqwest::Client; 14 10 pub type HttpError = reqwest::Error; 15 11 16 - // @NOT We need async_trait here because we want this trait to be dyn compatible. 17 - #[async_trait::async_trait] 18 - trait HandleResolve: std::fmt::Debug { 12 + trait HandleResolve: fmt::Debug + Sync { 19 13 /// Resolve a handle or a DID to a DID and DID document. 20 - async fn resolve(&self, ident: &str) -> Result<(Box<Did>, DidDocument), ResolveError> { 14 + /// 15 + fn resolve<'s: 'a, 'a>( 16 + &'s self, 17 + ident: &'a str, 18 + ) -> BoxFuture<'a, Result<(Box<Did>, DidDocument), ResolveError>> { 21 19 match ident.parse::<Box<Did>>() { 22 20 Ok(did) => { 23 - let doc = self.resolve_did(&did).await?; 24 - let handle = doc.primary_alias().ok_or(ResolveError::InvalidDocument)?; 21 + async { 22 + let doc = self.resolve_did(&did).await?; 23 + let handle = doc.primary_alias().ok_or(ResolveError::InvalidDocument)?; 25 24 26 - // Verify the primary handle in the DID document resolves to 27 - // the DID we were given. 28 - let resolved = self.resolve_handle(handle).await?; 29 - match did == resolved { 30 - true => Ok((did, doc)), 31 - false => Err(ResolveError::BidirectionalFailure), 25 + // Verify the primary handle in the DID document resolves to 26 + // the DID we were given. 27 + let resolved = self.resolve_handle(handle).await?; 28 + match did == resolved { 29 + true => Ok((did, doc)), 30 + false => Err(ResolveError::BidirectionalFailure), 31 + } 32 32 } 33 + .boxed() 33 34 } 34 35 Err(_) => { 35 36 let handle = ident.trim_start_matches('@'); 36 - let did = self.resolve_handle(handle).await?; 37 - let doc = self.resolve_did(&did).await?; 37 + async move { 38 + let did = self.resolve_handle(handle).await?; 39 + let doc = self.resolve_did(&did).await?; 38 40 39 - // Verify the document has a matching handle. 40 - for alias in &doc.also_known_as { 41 - if alias.domain().is_some_and(|host| host == ident) { 42 - return Ok((did, doc)); 41 + // Verify the document has a matching handle. 42 + for alias in &doc.also_known_as { 43 + if alias.domain().is_some_and(|host| host == ident) { 44 + return Ok((did, doc)); 45 + } 43 46 } 44 - } 45 47 46 - Err(ResolveError::BidirectionalFailure) 48 + Err(ResolveError::BidirectionalFailure) 49 + } 50 + .boxed() 47 51 } 48 52 } 49 53 } 50 54 51 55 /// Resolve a handle to a DID. 52 56 /// 57 + /// Implementors are not required to bi-directionally confirm the resolution. 58 + /// 59 + /// [`HandleResolve::resolve`] should be preferred. 60 + /// 53 61 /// Related: <https://docs.bsky.app/docs/api/com-atproto-identity-resolve-handle> 54 - async fn resolve_handle(&self, handle: &str) -> Result<Box<Did>, ResolveError>; 62 + fn resolve_handle<'s: 'h, 'h>( 63 + &'s self, 64 + handle: &'h str, 65 + ) -> BoxFuture<'h, Result<Box<Did>, ResolveError>>; 55 66 56 67 /// Resolve a DID to DID document. 57 68 /// 58 - /// Related: <https://docs.bsky.app/docs/api/com-atproto-identity-resolve-did> 59 - async fn resolve_did(&self, did: &Did) -> Result<DidDocument, ResolveError>; 69 + /// Implementors are not required to bi-directionally confirm the resolution. 70 + /// 71 + /// [`HandleResolve::resolve`] should be preferred. 72 + /// 73 + /// Ref: <https://docs.bsky.app/docs/api/com-atproto-identity-resolve-did> 74 + fn resolve_did<'s: 'd, 'd>( 75 + &'s self, 76 + did: &'d Did, 77 + ) -> BoxFuture<'d, Result<DidDocument, ResolveError>>; 60 78 61 - async fn invalidate_did(&self, _: &Did) {} 79 + /// Instruct a caching resolver to remove any cached resolutions for the 80 + /// specified DID. 81 + /// 82 + /// This will have no effect on system-level caches, like DNS caches. 83 + /// 84 + fn invalidate_did<'s: 'd, 'd>(&'s self, _: &'d Did) -> BoxFuture<'d, ()> { 85 + async {}.boxed() 86 + } 62 87 } 63 88 64 89 #[derive(Debug, thiserror::Error)] ··· 141 108 } 142 109 } 143 110 144 - #[async_trait::async_trait] 145 111 impl HandleResolve for Resolver { 146 - async fn resolve(&self, ident: &str) -> Result<(Box<Did>, DidDocument), ResolveError> { 147 - Self::resolve(self, ident).await 112 + fn resolve<'s: 'i, 'i>( 113 + &'s self, 114 + ident: &'i str, 115 + ) -> BoxFuture<'i, Result<(Box<Did>, DidDocument), ResolveError>> { 116 + Self::resolve(self, ident).boxed() 148 117 } 149 118 150 - async fn resolve_handle(&self, handle: &str) -> Result<Box<Did>, ResolveError> { 151 - Self::resolve_handle(self, handle).await 119 + fn resolve_handle<'s: 'h, 'h>( 120 + &'s self, 121 + handle: &'h str, 122 + ) -> BoxFuture<'h, Result<Box<Did>, ResolveError>> { 123 + Self::resolve_handle(self, handle).boxed() 152 124 } 153 125 154 - async fn resolve_did(&self, did: &Did) -> Result<DidDocument, ResolveError> { 155 - Self::resolve_did(self, did).await 126 + fn resolve_did<'s: 'd, 'd>( 127 + &'s self, 128 + did: &'d Did, 129 + ) -> BoxFuture<'d, Result<DidDocument, ResolveError>> { 130 + Self::resolve_did(self, did).boxed() 156 131 } 157 132 158 - async fn invalidate_did(&self, did: &Did) { 159 - Self::invalidate_did(&self, did).await 133 + fn invalidate_did<'s: 'd, 'd>(&'s self, did: &'d Did) -> BoxFuture<'d, ()> { 134 + Self::invalidate_did(self, did).boxed() 160 135 } 161 136 } 162 137
+32 -18
crates/identity/src/resolvers/direct.rs
··· 1 - use crate::{DEFAULT_PLC_DIRECTORY, Did, DidDocument, HandleResolve, HttpClient, ResolveError}; 1 + use std::borrow::Cow; 2 + 3 + use futures_util::{FutureExt as _, future::BoxFuture}; 2 4 use hickory_resolver::name_server::TokioConnectionProvider; 3 5 use hickory_resolver::{ 4 6 ResolveError as DnsResolveError, Resolver as DnsClient, TokioResolver, 5 7 name_server::ConnectionProvider, 6 8 }; 7 - use std::borrow::Cow; 8 9 use tokio::time::Instant; 10 + 11 + use crate::{DEFAULT_PLC_DIRECTORY, Did, DidDocument, HandleResolve, HttpClient, ResolveError}; 9 12 10 13 pub struct DirectResolver<'plc, R: ConnectionProvider> { 11 14 plc: Cow<'plc, str>, ··· 38 35 } 39 36 } 40 37 41 - #[async_trait::async_trait] 42 38 impl<R: ConnectionProvider> HandleResolve for DirectResolver<'_, R> { 43 - async fn resolve_handle(&self, handle: &str) -> Result<Box<Did>, ResolveError> { 39 + fn resolve_handle<'s: 'h, 'h>( 40 + &'s self, 41 + handle: &'h str, 42 + ) -> BoxFuture<'h, Result<Box<Did>, ResolveError>> { 44 43 let dns = resolve_handle_dns(&self.dns, handle); 45 44 let http = resolve_handle_http(&self.http, handle); 46 45 ··· 51 46 // technically incorrect. 52 47 53 48 let start = Instant::now(); 54 - tokio::select! { 55 - Ok(Some(did)) = dns => { 56 - tracing::trace!(?handle, %did, elapsed = ?start.elapsed(), "resolved via dns"); 57 - Ok(did) 58 - }, 59 - Ok(Some(did)) = http => { 60 - tracing::trace!(?handle, %did, elapsed = ?start.elapsed(), "resolved via http"); 61 - Ok(did) 49 + async move { 50 + tokio::select! { 51 + Ok(Some(did)) = dns => { 52 + tracing::trace!(?handle, %did, elapsed = ?start.elapsed(), "resolved via dns"); 53 + Ok(did) 54 + }, 55 + Ok(Some(did)) = http => { 56 + tracing::trace!(?handle, %did, elapsed = ?start.elapsed(), "resolved via http"); 57 + Ok(did) 58 + } 59 + else => Err(ResolveError::UnresolvedHandle), 62 60 } 63 - else => Err(ResolveError::UnresolvedHandle), 64 61 } 62 + .boxed() 65 63 } 66 64 67 - async fn resolve_did(&self, did: &Did) -> Result<DidDocument, ResolveError> { 68 - match did.method() { 69 - "plc" => Ok(self.fetch_plc_did_document(did).await?), 70 - "web" => Ok(self.fetch_web_did_document(did).await?), 71 - method => Err(ResolveError::UnsupportedDidMethod(method.to_string())), 65 + fn resolve_did<'s: 'd, 'd>( 66 + &'s self, 67 + did: &'d Did, 68 + ) -> BoxFuture<'d, Result<DidDocument, ResolveError>> { 69 + async { 70 + match did.method() { 71 + "plc" => Ok(self.fetch_plc_did_document(did).await?), 72 + "web" => Ok(self.fetch_web_did_document(did).await?), 73 + method => Err(ResolveError::UnsupportedDidMethod(method.to_string())), 74 + } 72 75 } 76 + .boxed() 73 77 } 74 78 } 75 79
+38 -21
crates/identity/src/resolvers/memcache.rs
··· 1 - use super::direct::DirectResolver; 2 - use crate::{DEFAULT_PLC_DIRECTORY, Did, DidDocument, HandleResolve, HttpClient, ResolveError}; 1 + use std::{borrow::Cow, time::Duration}; 2 + 3 + use futures_util::{FutureExt as _, future::BoxFuture}; 3 4 use hickory_resolver::name_server::TokioConnectionProvider; 4 5 use moka::sync::{Cache, CacheBuilder}; 5 - use std::{borrow::Cow, time::Duration}; 6 + 7 + use crate::{DEFAULT_PLC_DIRECTORY, Did, DidDocument, HandleResolve, HttpClient, ResolveError}; 8 + 9 + use super::direct::DirectResolver; 6 10 7 11 const DEFAULT_DID_CACHE_CAP: u64 = 1024; 8 12 const DEFAULT_DOC_CACHE_CAP: u64 = 1024; ··· 33 29 } 34 30 } 35 31 36 - #[async_trait::async_trait] 37 32 impl HandleResolve for MemcacheResolver { 38 - async fn resolve_handle(&self, handle: &str) -> Result<Box<Did>, ResolveError> { 39 - if let Some(did) = self.did_cache.get(handle) { 40 - tracing::trace!(?handle, ?did, "reusing resolved did from cache"); 41 - return Ok(did); 42 - } 33 + fn resolve_handle<'s: 'h, 'h>( 34 + &'s self, 35 + handle: &'h str, 36 + ) -> BoxFuture<'h, Result<Box<Did>, ResolveError>> { 37 + async move { 38 + if let Some(did) = self.did_cache.get(handle) { 39 + tracing::trace!(?handle, ?did, "reusing resolved did from cache"); 40 + return Ok(did); 41 + } 43 42 44 - let did = self.inner.resolve_handle(handle).await?; 45 - self.did_cache.insert(handle.into(), did.clone()); 46 - Ok(did) 43 + let did = self.inner.resolve_handle(handle).await?; 44 + self.did_cache.insert(handle.into(), did.clone()); 45 + Ok(did) 46 + } 47 + .boxed() 47 48 } 48 49 49 - async fn resolve_did(&self, did: &Did) -> Result<DidDocument, ResolveError> { 50 - if let Some(doc) = self.doc_cache.get(&did.to_owned()) { 51 - tracing::trace!(?did, "reusing resolved did document from cache"); 52 - return Ok(doc); 53 - } 50 + fn resolve_did<'s: 'd, 'd>( 51 + &'s self, 52 + did: &'d Did, 53 + ) -> BoxFuture<'d, Result<DidDocument, ResolveError>> { 54 + async move { 55 + if let Some(doc) = self.doc_cache.get(&did.to_owned()) { 56 + tracing::trace!(?did, "reusing resolved did document from cache"); 57 + return Ok(doc); 58 + } 54 59 55 - let doc = self.inner.resolve_did(did).await?; 56 - self.doc_cache.insert(did.to_owned(), doc.clone()); 57 - Ok(doc) 60 + let doc = self.inner.resolve_did(did).await?; 61 + self.doc_cache.insert(did.to_owned(), doc.clone()); 62 + Ok(doc) 63 + } 64 + .boxed() 58 65 } 59 66 60 - async fn invalidate_did(&self, did: &Did) { 67 + fn invalidate_did<'s: 'd, 'd>(&'s self, did: &'d Did) -> BoxFuture<'d, ()> { 61 68 if let Some(doc) = self.doc_cache.remove(did) { 62 69 tracing::trace!(?did, "invalidating DID document"); 63 70 for handle in doc.also_known_as.iter().filter_map(|uri| uri.domain()) { ··· 76 61 self.did_cache.remove(handle); 77 62 } 78 63 } 64 + 65 + async {}.boxed() 79 66 } 80 67 } 81 68