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