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}