identity_kit/resolver/mod.rs
1//! DID resolution and production: the [`DidResolver`] and [`DidProducer`] traits a DID method
2//! implements, the [`Resolver`] registry that routes a DID to the resolver of its method, an
3//! optional TTL [`DocumentCache`], and DID URL dereferencing.
4//!
5//! The module ships no DID method. Each method is a type implementing [`DidResolver`] and/or
6//! [`DidProducer`], registered with [`ResolverBuilder::with`]; adding one needs no change here.
7//!
8//! # Resolution
9//!
10//! [`Resolver::resolve`] implements the DID Resolution algorithm around the method's resolver:
11//! it reports [`ResolutionError::MethodNotSupported`] for a method nobody registered, turns a
12//! result whose metadata reports an error into `Err`, refuses a document whose `id` is not the
13//! DID being resolved (nor its `canonicalId`), and serves repeated resolutions from the cache
14//! when one is configured. [`Resolver::resolve_document`] is the shortcut to the document.
15//!
16//! # Dereferencing
17//!
18//! [`Resolver::dereference`] resolves the DID of a DID URL and then selects the resource the URL
19//! names: the document itself, or the verification method or service whose `id` is the URL
20//! ([`Dereferenced`]). The `versionId` and `versionTime` DID parameters are passed to the
21//! method's resolver as resolution options; a path, any other query parameter and the
22//! `service`/`relativeRef` parameters are method-specific and reported as
23//! [`DereferenceError::MethodSpecific`]. [`Resolver::resolve_public_key`] goes one step further
24//! and returns the [`Jwk`](crate::jose::Jwk) of a verification method, optionally checked to be
25//! usable for a verification relationship, decoding `publicKeyMultibase` material as a Multikey,
26//! which is what a JWS verifier needs.
27//!
28//! # Examples
29//!
30//! A method is a type implementing [`DidResolver`]; here one that serves documents from memory.
31//!
32//! ```
33//! use std::collections::HashMap;
34//!
35//! use identity_kit::did::{
36//! Did, DidDocument, DidUrl, ResolutionError, ResolutionOptions, ResolutionResult,
37//! };
38//! use identity_kit::resolver::{Dereferenced, DidResolver, Resolver};
39//!
40//! struct Static(HashMap<Did, DidDocument>);
41//!
42//! #[async_trait::async_trait]
43//! impl DidResolver for Static {
44//! fn method(&self) -> &'static str {
45//! "example"
46//! }
47//!
48//! async fn resolve(
49//! &self,
50//! did: &Did,
51//! _: &ResolutionOptions,
52//! ) -> Result<ResolutionResult, ResolutionError> {
53//! self.0
54//! .get(did)
55//! .cloned()
56//! .map(ResolutionResult::new)
57//! .ok_or(ResolutionError::NotFound)
58//! }
59//! }
60//!
61//! # tokio::runtime::Builder::new_current_thread().build()?.block_on(async {
62//! let document: DidDocument = serde_json::from_str(r##"{
63//! "id": "did:example:123",
64//! "verificationMethod": [{
65//! "id": "did:example:123#key-1",
66//! "type": "JsonWebKey2020",
67//! "controller": "did:example:123",
68//! "publicKeyJwk": {
69//! "kty": "OKP", "crv": "Ed25519", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
70//! }
71//! }],
72//! "authentication": ["#key-1"]
73//! }"##)?;
74//! let did = document.id.clone();
75//! let resolver = Resolver::builder()
76//! .with(Static(HashMap::from([(did.clone(), document)])))
77//! .cache(std::time::Duration::from_secs(60))
78//! .build();
79//!
80//! assert_eq!(resolver.resolve_document(&did).await?.id, did);
81//!
82//! let key = DidUrl::parse("did:example:123#key-1")?;
83//! let jwk = resolver.resolve_public_key(&key, None).await?;
84//! assert_eq!(jwk.crv(), Some("Ed25519"));
85//! assert!(matches!(resolver.dereference(&key).await?, Dereferenced::VerificationMethod(_)));
86//!
87//! let unknown = Did::parse("did:other:123")?;
88//! assert_eq!(
89//! resolver.resolve(&unknown).await.unwrap_err(),
90//! ResolutionError::MethodNotSupported
91//! );
92//! # Ok::<(), Box<dyn std::error::Error>>(())
93//! # })?;
94//! # Ok::<(), Box<dyn std::error::Error>>(())
95//! ```
96
97use std::sync::Arc;
98
99use crate::did::{Did, ResolutionError, ResolutionOptions, ResolutionResult};
100
101mod cache;
102mod dereference;
103mod producer;
104mod registry;
105
106pub use cache::DocumentCache;
107pub use dereference::{DereferenceError, Dereferenced};
108pub use producer::{DidProducer, Hosting, MethodCapabilities, ProduceError};
109pub use registry::{Resolver, ResolverBuilder};
110
111/// Resolves the DIDs of one method (DID Resolution "DID Resolution Algorithm").
112///
113/// A resolver is registered with a [`Resolver`] under [`method`](Self::method) and receives
114/// only DIDs of that method. It is `Send + Sync` and resolution is `async` because most methods
115/// fetch the document over the network or from a registry.
116///
117/// A failed resolution may be reported either as `Err`, or as an `Ok` result whose resolution
118/// metadata carries the error, which is the shape a method that builds a full
119/// [`ResolutionResult`] (a client of a remote resolver, say) produces anyway. The [`Resolver`]
120/// normalizes both to `Err`.
121#[async_trait::async_trait]
122pub trait DidResolver: Send + Sync {
123 /// The method name this resolver handles: `web` for `did:web`.
124 fn method(&self) -> &'static str;
125
126 /// Resolves `did`, whose method is [`method`](Self::method), with `options`.
127 ///
128 /// On success the result holds the document with `id` equal to `did` (or to the
129 /// `canonicalId` of the document metadata) and whatever metadata the method knows. A
130 /// deactivated DID resolves to a result without a document whose document metadata has
131 /// `deactivated: true`.
132 ///
133 /// # Errors
134 ///
135 /// A [`ResolutionError`]: [`NotFound`](ResolutionError::NotFound) for a DID that does not
136 /// exist, [`RepresentationNotSupported`](ResolutionError::RepresentationNotSupported) for an
137 /// `accept` the method cannot produce, [`InternalError`](ResolutionError::InternalError) for
138 /// an unexpected failure such as a network error.
139 async fn resolve(
140 &self,
141 did: &Did,
142 options: &ResolutionOptions,
143 ) -> Result<ResolutionResult, ResolutionError>;
144}
145
146macro_rules! forward_did_resolver {
147 ($($ty:ty),* $(,)?) => {$(
148 #[async_trait::async_trait]
149 impl<T: DidResolver + ?Sized> DidResolver for $ty {
150 fn method(&self) -> &'static str {
151 (**self).method()
152 }
153
154 async fn resolve(
155 &self,
156 did: &Did,
157 options: &ResolutionOptions,
158 ) -> Result<ResolutionResult, ResolutionError> {
159 (**self).resolve(did, options).await
160 }
161 }
162 )*};
163}
164
165forward_did_resolver!(&T, Box<T>, Arc<T>);