Skip to main content

identity_kit/resolver/
registry.rs

1//! `Resolver`: the registry of method resolvers, and its builder.
2
3use core::fmt;
4use std::collections::HashMap;
5use std::sync::Arc;
6use std::time::Duration;
7
8use super::{DidResolver, DocumentCache};
9use crate::did::{Did, DidDocument, ResolutionError, ResolutionOptions, ResolutionResult};
10
11/// Routes each DID to the [`DidResolver`] registered for its method, with an optional
12/// [`DocumentCache`] in front.
13///
14/// Built with [`Resolver::builder`]. The resolver is `Send + Sync` and meant to be shared, in an
15/// `Arc` or by reference, by every task of an application. See the [module
16/// documentation](super) for the resolution and dereferencing semantics and an example.
17pub struct Resolver {
18    methods: HashMap<String, Arc<dyn DidResolver>>,
19    cache: Option<DocumentCache>,
20}
21
22impl Resolver {
23    /// A builder with no method registered and no cache.
24    pub fn builder() -> ResolverBuilder {
25        ResolverBuilder::new()
26    }
27
28    /// The registered method names, sorted.
29    pub fn methods(&self) -> Vec<&str> {
30        let mut methods: Vec<&str> = self.methods.keys().map(String::as_str).collect();
31        methods.sort_unstable();
32        methods
33    }
34
35    /// Whether a resolver for `method` (`web`, not `did:web`) is registered.
36    pub fn supports(&self, method: &str) -> bool {
37        self.methods.contains_key(method)
38    }
39
40    /// The resolver registered for `method`, if any.
41    pub fn resolver(&self, method: &str) -> Option<&Arc<dyn DidResolver>> {
42        self.methods.get(method)
43    }
44
45    /// The cache, when the resolver was built with one.
46    pub fn cache(&self) -> Option<&DocumentCache> {
47        self.cache.as_ref()
48    }
49
50    /// Resolves `did` without options, through the cache when there is one.
51    ///
52    /// On success the result holds the document, or no document and `deactivated: true` in its
53    /// document metadata when the DID has been deactivated.
54    ///
55    /// # Errors
56    ///
57    /// [`ResolutionError::MethodNotSupported`] when no resolver is registered for the method
58    /// of `did`; otherwise what the method's resolver reports, as `Err` or in its resolution
59    /// metadata; [`ResolutionError::NotFound`] when it reports neither a document nor an error
60    /// nor deactivation; [`ResolutionError::InvalidDidDocument`] when the document's `id` is
61    /// neither `did` nor the `canonicalId` of the document metadata.
62    pub async fn resolve(&self, did: &Did) -> Result<ResolutionResult, ResolutionError> {
63        self.resolve_with(did, &ResolutionOptions::default()).await
64    }
65
66    /// Resolves `did` with `options`, bypassing the cache unless `options` is empty.
67    ///
68    /// # Errors
69    ///
70    /// As [`resolve`](Self::resolve).
71    pub async fn resolve_with(
72        &self,
73        did: &Did,
74        options: &ResolutionOptions,
75    ) -> Result<ResolutionResult, ResolutionError> {
76        let resolver = self
77            .methods
78            .get(did.method())
79            .ok_or(ResolutionError::MethodNotSupported)?;
80        let cache = self
81            .cache
82            .as_ref()
83            .filter(|_| options.accept.is_none() && options.properties.is_empty());
84        if let Some(hit) = cache.and_then(|cache| cache.get(did)) {
85            return Ok(hit);
86        }
87        let result = check_result(did, resolver.resolve(did, options).await?)?;
88        if let Some(cache) = cache {
89            cache.insert(did.clone(), result.clone());
90        }
91        Ok(result)
92    }
93
94    /// The DID document of `did`.
95    ///
96    /// # Errors
97    ///
98    /// As [`resolve`](Self::resolve); a deactivated DID is [`ResolutionError::NotFound`], even
99    /// when the method still returns its last document.
100    pub async fn resolve_document(&self, did: &Did) -> Result<DidDocument, ResolutionError> {
101        let result = self.resolve(did).await?;
102        if result.document_metadata.is_deactivated() {
103            return Err(ResolutionError::NotFound);
104        }
105        result.into_document()
106    }
107}
108
109/// Normalizes what a method resolver returned: an error in the metadata becomes `Err`, a
110/// result with nothing in it is `notFound`, and the document must be the one asked for.
111fn check_result(did: &Did, result: ResolutionResult) -> Result<ResolutionResult, ResolutionError> {
112    if let Some(error) = &result.resolution_metadata.error {
113        return Err(error.clone());
114    }
115    match &result.document {
116        Some(document) => {
117            let canonical = result.document_metadata.canonical_id.as_ref();
118            if document.id != *did && canonical != Some(&document.id) {
119                return Err(ResolutionError::InvalidDidDocument);
120            }
121        }
122        None if result.document_metadata.is_deactivated() => {}
123        None => return Err(ResolutionError::NotFound),
124    }
125    Ok(result)
126}
127
128impl fmt::Debug for Resolver {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        f.debug_struct("Resolver")
131            .field("methods", &self.methods())
132            .field("cache", &self.cache)
133            .finish()
134    }
135}
136
137/// Builds a [`Resolver`].
138///
139/// # Examples
140///
141/// ```
142/// use std::time::Duration;
143///
144/// use identity_kit::did::{Did, ResolutionError, ResolutionOptions, ResolutionResult};
145/// use identity_kit::resolver::{DidResolver, Resolver};
146///
147/// struct Nothing;
148///
149/// #[async_trait::async_trait]
150/// impl DidResolver for Nothing {
151///     fn method(&self) -> &'static str {
152///         "example"
153///     }
154///
155///     async fn resolve(
156///         &self,
157///         _: &Did,
158///         _: &ResolutionOptions,
159///     ) -> Result<ResolutionResult, ResolutionError> {
160///         Err(ResolutionError::NotFound)
161///     }
162/// }
163///
164/// let resolver = Resolver::builder()
165///     .with(Nothing)
166///     .cache(Duration::from_secs(60))
167///     .build();
168/// assert_eq!(resolver.methods(), ["example"]);
169/// assert!(resolver.supports("example") && !resolver.supports("web"));
170/// assert_eq!(resolver.cache().map(|cache| cache.ttl()), Some(Duration::from_secs(60)));
171/// ```
172#[derive(Default)]
173pub struct ResolverBuilder {
174    methods: HashMap<String, Arc<dyn DidResolver>>,
175    cache: Option<DocumentCache>,
176}
177
178impl ResolverBuilder {
179    /// A builder with no method registered and no cache.
180    pub fn new() -> Self {
181        Self::default()
182    }
183
184    /// Registers `resolver` for its method, replacing any resolver registered for the same
185    /// method before, so a later registration overrides a default.
186    #[must_use]
187    pub fn with(self, resolver: impl DidResolver + 'static) -> Self {
188        self.with_arc(Arc::new(resolver))
189    }
190
191    /// Registers an already shared `resolver`, as [`with`](Self::with).
192    #[must_use]
193    pub fn with_arc(mut self, resolver: Arc<dyn DidResolver>) -> Self {
194        self.methods.insert(resolver.method().to_owned(), resolver);
195        self
196    }
197
198    /// Caches resolution results for `ttl` in a [`DocumentCache`] of default capacity.
199    #[must_use]
200    pub fn cache(self, ttl: Duration) -> Self {
201        self.with_cache(DocumentCache::new(ttl))
202    }
203
204    /// Caches resolution results in `cache`.
205    #[must_use]
206    pub fn with_cache(mut self, cache: DocumentCache) -> Self {
207        self.cache = Some(cache);
208        self
209    }
210
211    /// The resolver.
212    pub fn build(self) -> Resolver {
213        Resolver {
214            methods: self.methods,
215            cache: self.cache,
216        }
217    }
218}
219
220impl fmt::Debug for ResolverBuilder {
221    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222        let mut methods: Vec<&str> = self.methods.keys().map(String::as_str).collect();
223        methods.sort_unstable();
224        f.debug_struct("ResolverBuilder")
225            .field("methods", &methods)
226            .field("cache", &self.cache)
227            .finish()
228    }
229}