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}