Skip to main content

affinidi_did_resolver_traits/
lib.rs

1/*!
2 * Pluggable DID resolution traits.
3 *
4 * Provides [`Resolver`] (sync) and [`AsyncResolver`] (async) traits for
5 * decoupling DID resolution from concrete types. External consumers implement
6 * these traits for custom DID methods; the SDK composes them with built-in
7 * resolvers.
8 *
9 * Every [`Resolver`] is automatically an [`AsyncResolver`] via blanket impl,
10 * so the SDK only needs `Box<dyn AsyncResolver>` for composition.
11 *
12 * # Return Convention
13 *
14 * Resolvers return `Option<Result<Document, ResolverError>>`:
15 * - `None` — "not my DID, pass to next resolver"
16 * - `Some(Ok(doc))` — resolved successfully
17 * - `Some(Err(e))` — recognized the DID but resolution failed
18 */
19
20use std::future::Future;
21use std::pin::Pin;
22
23mod error;
24mod resolvers;
25
26pub use error::ResolverError;
27pub use resolvers::{KeyResolver, PeerResolver};
28
29use affinidi_did_common::{DID, DIDMethod, Document};
30
31/// Result type alias for resolver return values.
32///
33/// - `None`: this resolver does not handle the given DID method
34/// - `Some(Ok(doc))`: resolved successfully
35/// - `Some(Err(e))`: this resolver handles the method but resolution failed
36pub type Resolution = Option<Result<Document, ResolverError>>;
37
38/// Discriminant for DID method types — used as HashMap key for resolver dispatch.
39///
40/// Unlike `DIDMethod` (which carries parsed data), this is a pure tag type
41/// suitable for `Hash + Eq` keying. Derived from `DIDMethod` via `From` impl
42/// in the SDK crate.
43#[derive(Debug, Clone, Hash, PartialEq, Eq)]
44pub enum MethodName {
45    Key,
46    Peer,
47    Web,
48    Jwk,
49    Ethr,
50    Pkh,
51    Webvh,
52    Cheqd,
53    Scid,
54    Ebsi,
55    /// Catch-all for methods not explicitly modeled.
56    Other(String),
57}
58
59impl std::fmt::Display for MethodName {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        match self {
62            MethodName::Key => write!(f, "key"),
63            MethodName::Peer => write!(f, "peer"),
64            MethodName::Web => write!(f, "web"),
65            MethodName::Jwk => write!(f, "jwk"),
66            MethodName::Ethr => write!(f, "ethr"),
67            MethodName::Pkh => write!(f, "pkh"),
68            MethodName::Webvh => write!(f, "webvh"),
69            MethodName::Cheqd => write!(f, "cheqd"),
70            MethodName::Scid => write!(f, "scid"),
71            MethodName::Ebsi => write!(f, "ebsi"),
72            MethodName::Other(s) => write!(f, "{s}"),
73        }
74    }
75}
76
77/// Convert `DIDMethod` (with data) to `MethodName` (pure discriminant).
78impl From<&DIDMethod> for MethodName {
79    fn from(method: &DIDMethod) -> Self {
80        match method.name() {
81            "key" => MethodName::Key,
82            "peer" => MethodName::Peer,
83            "web" => MethodName::Web,
84            "jwk" => MethodName::Jwk,
85            "ethr" => MethodName::Ethr,
86            "pkh" => MethodName::Pkh,
87            "webvh" => MethodName::Webvh,
88            "cheqd" => MethodName::Cheqd,
89            "scid" => MethodName::Scid,
90            "ebsi" => MethodName::Ebsi,
91            method => MethodName::Other(method.to_string()),
92        }
93    }
94}
95
96/// Synchronous DID resolver for methods that require no IO.
97///
98/// Implement this for methods where resolution is pure computation
99/// (e.g., `did:key`, `did:peer`). Every `Resolver` is automatically
100/// an [`AsyncResolver`] via blanket impl.
101pub trait Resolver: Send + Sync {
102    /// Human-readable name for this resolver (e.g., `"KeyResolver"`).
103    ///
104    /// Must be unique within a method's resolver chain. Used by
105    /// `find_resolver()` to locate resolvers by name.
106    fn name(&self) -> &str;
107
108    /// Attempt to resolve the given DID to a Document.
109    fn resolve(&self, did: &DID) -> Resolution;
110}
111
112/// Asynchronous DID resolver for methods that require IO.
113///
114/// Implement this directly for methods that need network access,
115/// database lookups, or other async operations (e.g., `did:web`, `did:ethr`).
116///
117/// Sync resolvers get this for free via the blanket impl.
118///
119/// This trait is dyn-compatible: the SDK stores resolvers as
120/// `Box<dyn AsyncResolver>` for composition.
121pub trait AsyncResolver: Send + Sync {
122    /// Human-readable name for this resolver (e.g., `"EthrResolver"`).
123    ///
124    /// Must be unique within a method's resolver chain. Used by
125    /// `find_resolver()` to locate resolvers by name.
126    fn name(&self) -> &str;
127
128    /// Attempt to resolve the given DID to a Document.
129    fn resolve<'a>(&'a self, did: &'a DID)
130    -> Pin<Box<dyn Future<Output = Resolution> + Send + 'a>>;
131}
132
133/// Every sync [`Resolver`] is automatically an [`AsyncResolver`].
134impl<T: Resolver> AsyncResolver for T {
135    fn name(&self) -> &str {
136        Resolver::name(self)
137    }
138
139    fn resolve<'a>(
140        &'a self,
141        did: &'a DID,
142    ) -> Pin<Box<dyn Future<Output = Resolution> + Send + 'a>> {
143        Box::pin(std::future::ready(Resolver::resolve(self, did)))
144    }
145}