ssi_dids_core/
resolution.rs

1use std::collections::BTreeMap;
2
3use iref::IriRefBuf;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    document::{self, representation, DIDVerificationMethod, InvalidData},
8    DIDMethod, Document, PrimaryDIDURL, VerificationMethodDIDResolver, DID, DIDURL,
9};
10
11mod composition;
12mod dereference;
13mod static_resolver;
14
15pub use dereference::*;
16pub use static_resolver::StaticDIDResolver;
17
18#[cfg(feature = "http")]
19mod http;
20
21#[cfg(feature = "http")]
22pub use http::*;
23
24/// Pseudo-media-type used when returning a URL from
25/// [DID URL dereferencing](DIDResolver::dereference).
26pub const MEDIA_TYPE_URL: &str = "text/url";
27
28/// DID resolution error.
29///
30/// Error raised by the [`DIDResolver::resolve`] method.
31#[derive(Debug, thiserror::Error)]
32pub enum Error {
33    /// DID method is not supported by this resolver.
34    #[error("DID method `{0}` not supported")]
35    MethodNotSupported(String),
36
37    /// DID document could not be found.
38    #[error("DID document not found")]
39    NotFound,
40
41    /// Resolver doesn't know what representation to use for the DID document.
42    #[error("no representation specified")]
43    NoRepresentation,
44
45    /// Requested DID document representation is not supported.
46    #[error("DID representation `{0}` not supported")]
47    RepresentationNotSupported(String),
48
49    /// Invalid data provided to the resolver.
50    #[error(transparent)]
51    InvalidData(InvalidData),
52
53    /// Invalid method-specific identifier.
54    #[error("invalid method specific identifier: {0}")]
55    InvalidMethodSpecificId(String),
56
57    /// Invalid resolution options.
58    #[error("invalid options")]
59    InvalidOptions,
60
61    /// Internal resolver-specific error.
62    #[error("DID resolver internal error: {0}")]
63    Internal(String),
64}
65
66impl Error {
67    /// Creates a new internal error.
68    pub fn internal(error: impl ToString) -> Self {
69        Self::Internal(error.to_string())
70    }
71
72    /// Returns the error kind.
73    pub fn kind(&self) -> ErrorKind {
74        match self {
75            Self::MethodNotSupported(_) => ErrorKind::MethodNotSupported,
76            Self::NotFound => ErrorKind::NotFound,
77            Self::NoRepresentation => ErrorKind::NoRepresentation,
78            Self::RepresentationNotSupported(_) => ErrorKind::RepresentationNotSupported,
79            Self::InvalidData(_) => ErrorKind::InvalidData,
80            Self::InvalidMethodSpecificId(_) => ErrorKind::InvalidMethodSpecificId,
81            Self::InvalidOptions => ErrorKind::InvalidOptions,
82            Self::Internal(_) => ErrorKind::Internal,
83        }
84    }
85}
86
87impl From<representation::Unknown> for Error {
88    fn from(value: representation::Unknown) -> Self {
89        Self::RepresentationNotSupported(value.0)
90    }
91}
92
93/// Resolution error kind.
94///
95/// Each resolution [`Error`] has a kind provided by the [`Error::kind`] method.
96#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
97pub enum ErrorKind {
98    MethodNotSupported,
99    NotFound,
100    NoRepresentation,
101    RepresentationNotSupported,
102    InvalidData,
103    InvalidMethodSpecificId,
104    InvalidOptions,
105    Internal,
106}
107
108pub trait DIDResolverByMethod {
109    type MethodResolver: DIDMethodResolver;
110
111    /// Returns a resolver for the given method name, if any.
112    fn get_method(&self, method_name: &str) -> Option<&Self::MethodResolver>;
113
114    fn supports_method(&self, method_name: &str) -> bool {
115        self.get_method(method_name).is_some()
116    }
117}
118
119impl<T: DIDResolverByMethod> DIDResolver for T {
120    async fn resolve_representation<'a>(
121        &'a self,
122        did: &'a DID,
123        options: Options,
124    ) -> Result<Output<Vec<u8>>, Error> {
125        match self.get_method(did.method_name()) {
126            Some(m) => {
127                m.resolve_method_representation(did.method_specific_id(), options)
128                    .await
129            }
130            None => Err(Error::MethodNotSupported(did.method_name().to_string())),
131        }
132    }
133}
134
135/// [DID resolver](https://www.w3.org/TR/did-core/#dfn-did-resolvers).
136///
137/// Any type implementing the [DID Resolution](https://www.w3.org/TR/did-core/#did-resolution)
138/// [algorithm](https://w3c-ccg.github.io/did-resolution/#resolving-algorithm)
139/// through the [`resolve`](DIDResolver::resolve) method
140/// and the [DID URL Dereferencing](https://www.w3.org/TR/did-core/#did-url-dereferencing)
141/// algorithm through the [`dereference`](DIDResolver::dereference) method.
142///
143/// This library provides the [`AnyDidMethod`] that implements this trait
144/// by grouping various DID method implementations.
145///
146/// [`AnyDidMethod`]: <../dids/struct.AnyDidMethod.html>
147pub trait DIDResolver {
148    /// Resolves a DID representation.
149    ///
150    /// Fetches the DID document representation referenced by the input DID
151    /// using the given options.
152    ///
153    /// See: <https://www.w3.org/TR/did-core/#did-resolution>
154    #[allow(async_fn_in_trait)]
155    async fn resolve_representation<'a>(
156        &'a self,
157        did: &'a DID,
158        options: Options,
159    ) -> Result<Output<Vec<u8>>, Error>;
160
161    /// Resolves a DID with the given options.
162    ///
163    /// Fetches the DID document referenced by the input DID using the given
164    /// options.
165    ///
166    /// See: <https://www.w3.org/TR/did-core/#did-resolution>
167    #[allow(async_fn_in_trait)]
168    async fn resolve_with<'a>(&'a self, did: &'a DID, options: Options) -> Result<Output, Error> {
169        let output = self.resolve_representation(did, options).await?;
170        match &output.metadata.content_type {
171            None => Err(Error::NoRepresentation),
172            Some(ty) => {
173                let ty: representation::MediaType = ty.parse()?;
174                output
175                    .try_map(|bytes| Document::from_bytes(ty, &bytes))
176                    .map_err(Error::InvalidData)
177            }
178        }
179    }
180
181    /// Resolves a DID.
182    ///
183    /// Fetches the DID document referenced by the input DID using the default
184    /// options.
185    ///
186    /// See: <https://www.w3.org/TR/did-core/#did-resolution>
187    #[allow(async_fn_in_trait)]
188    async fn resolve<'a>(&'a self, did: &'a DID) -> Result<Output, Error> {
189        self.resolve_with(did, Options::default()).await
190    }
191
192    /// Resolves a DID and extracts one of the verification methods it defines.
193    ///
194    /// This will return the first verification method found, although users
195    /// should not expect the DID documents to always list verification methods
196    /// in the same order.
197    ///
198    /// See: [`Document::into_any_verification_method()`].
199    #[allow(async_fn_in_trait)]
200    async fn resolve_into_any_verification_method<'a>(
201        &'a self,
202        did: &'a DID,
203    ) -> Result<Option<DIDVerificationMethod>, Error> {
204        Ok(self
205            .resolve(did)
206            .await?
207            .document
208            .into_document()
209            .into_any_verification_method())
210    }
211
212    /// Dereference a DID URL to retrieve the primary content.
213    ///
214    /// See: <https://www.w3.org/TR/did-core/#did-url-dereferencing>
215    /// See: <https://w3c-ccg.github.io/did-resolution/#dereferencing-algorithm>
216    #[allow(async_fn_in_trait)]
217    async fn dereference_primary<'a>(
218        &'a self,
219        primary_did_url: &'a PrimaryDIDURL,
220    ) -> Result<DerefOutput<PrimaryContent>, DerefError> {
221        self.dereference_primary_with(primary_did_url, Options::default())
222            .await
223    }
224
225    /// Dereference a DID URL to retrieve the primary content.
226    ///
227    /// See: <https://www.w3.org/TR/did-core/#did-url-dereferencing>
228    /// See: <https://w3c-ccg.github.io/did-resolution/#dereferencing-algorithm>
229    #[allow(async_fn_in_trait)]
230    async fn dereference_primary_with<'a>(
231        &'a self,
232        primary_did_url: &'a PrimaryDIDURL,
233        mut resolve_options: Options,
234    ) -> Result<DerefOutput<PrimaryContent>, DerefError> {
235        // 2
236        resolve_options.extend(match primary_did_url.query() {
237            Some(query) => serde_urlencoded::from_str(query.as_str()).unwrap(),
238            None => Options::default(),
239        });
240
241        let parameters = resolve_options.parameters.clone();
242
243        let resolution_output = self
244            .resolve_with(primary_did_url.did(), resolve_options)
245            .await?;
246
247        dereference_primary_resource(self, primary_did_url, parameters, resolution_output).await
248    }
249
250    /// Dereference a DID URL with a path or query to retrieve the primary
251    /// content.
252    ///
253    /// This function is called from [`Self::dereference_primary()`] only if
254    /// the primary DID url has a path and/or query, and the query does not
255    /// include any service.
256    /// Users should always call [`Self::dereference_primary()`].
257    ///
258    /// See: <https://www.w3.org/TR/did-core/#did-url-dereferencing>
259    /// See: <https://w3c-ccg.github.io/did-resolution/#dereferencing-algorithm>
260    #[allow(async_fn_in_trait)]
261    async fn dereference_primary_with_path_or_query<'a>(
262        &'a self,
263        _primary_did_url: &'a PrimaryDIDURL,
264    ) -> Result<DerefOutput<PrimaryContent>, DerefError> {
265        Err(DerefError::NotFound)
266    }
267
268    /// Dereference a DID URL.
269    ///
270    /// See: <https://www.w3.org/TR/did-core/#did-url-dereferencing>
271    /// See: <https://w3c-ccg.github.io/did-resolution/#dereferencing-algorithm>
272    #[allow(async_fn_in_trait)]
273    async fn dereference_with<'a>(
274        &'a self,
275        did_url: &'a DIDURL,
276        options: Options,
277    ) -> Result<DerefOutput, DerefError> {
278        let (primary_did_url, fragment) = did_url.without_fragment();
279        let primary_deref_output = self
280            .dereference_primary_with(primary_did_url, options)
281            .await?;
282        // 4
283        match fragment {
284            Some(fragment) => {
285                dereference_secondary_resource(primary_did_url, fragment, primary_deref_output)
286            }
287            None => Ok(primary_deref_output.cast()),
288        }
289    }
290
291    /// Dereference a DID URL.
292    ///
293    /// See: <https://www.w3.org/TR/did-core/#did-url-dereferencing>
294    /// See: <https://w3c-ccg.github.io/did-resolution/#dereferencing-algorithm>
295    #[allow(async_fn_in_trait)]
296    async fn dereference<'a>(&'a self, did_url: &'a DIDURL) -> Result<DerefOutput, DerefError> {
297        self.dereference_with(did_url, Options::default()).await
298    }
299
300    /// Turns this DID resolver into a verification method resolver.
301    ///
302    /// To resolve a verification method, the output resolver will first
303    /// resolve the DID using the given `options` then pull the referenced
304    /// method from the DID document.
305    fn into_vm_resolver_with<M>(self, options: Options) -> VerificationMethodDIDResolver<Self, M>
306    where
307        Self: Sized,
308    {
309        VerificationMethodDIDResolver::new_with_options(self, options)
310    }
311
312    /// Turns this DID resolver into a verification method resolver.
313    ///
314    /// To resolve a verification method, the output resolver will first
315    /// resolve the DID then pull the referenced method from the DID document.
316    ///
317    /// This is equivalent to calling
318    /// [`into_vm_resolver_with`](DIDResolver::into_vm_resolver_with)
319    /// with the default options.
320    fn into_vm_resolver<M>(self) -> VerificationMethodDIDResolver<Self, M>
321    where
322        Self: Sized,
323    {
324        VerificationMethodDIDResolver::new(self)
325    }
326}
327
328pub trait DIDMethodResolver: DIDMethod {
329    /// Returns the name of the method handled by this resolver.
330    fn method_name(&self) -> &str {
331        Self::DID_METHOD_NAME
332    }
333
334    /// Resolves a DID representation using a method specific identifier.
335    ///
336    /// Fetches the DID document representation referenced by the input method
337    /// specific identifier using the given options.
338    ///
339    /// See: <https://www.w3.org/TR/did-core/#did-resolution>
340    #[allow(async_fn_in_trait)]
341    async fn resolve_method_representation<'a>(
342        &'a self,
343        method_specific_id: &'a str,
344        options: Options,
345    ) -> Result<Output<Vec<u8>>, Error>;
346}
347
348impl<T: DIDMethodResolver> DIDMethodResolver for &T {
349    fn method_name(&self) -> &str {
350        T::method_name(*self)
351    }
352
353    async fn resolve_method_representation<'b>(
354        &'b self,
355        method_specific_id: &'b str,
356        options: Options,
357    ) -> Result<Output<Vec<u8>>, Error> {
358        T::resolve_method_representation(*self, method_specific_id, options).await
359    }
360}
361
362impl<T: DIDMethodResolver> DIDResolverByMethod for T {
363    type MethodResolver = Self;
364
365    fn get_method(&self, method_name: &str) -> Option<&Self> {
366        if self.method_name() == method_name {
367            Some(self)
368        } else {
369            None
370        }
371    }
372}
373
374#[derive(Debug, Clone)]
375pub struct Output<T = document::Represented> {
376    pub document: T,
377    pub document_metadata: document::Metadata,
378    pub metadata: Metadata,
379}
380
381impl<T> Output<T> {
382    pub fn from_content(content: T, content_type: Option<String>) -> Self {
383        Self::new(
384            content,
385            document::Metadata::default(),
386            Metadata::from_content_type(content_type),
387        )
388    }
389
390    pub fn new(document: T, document_metadata: document::Metadata, metadata: Metadata) -> Self {
391        Self {
392            document,
393            document_metadata,
394            metadata,
395        }
396    }
397
398    pub fn try_map<U, E>(self, f: impl FnOnce(T) -> Result<U, E>) -> Result<Output<U>, E> {
399        Ok(Output {
400            document: f(self.document)?,
401            document_metadata: self.document_metadata,
402            metadata: self.metadata,
403        })
404    }
405}
406
407/// Resolution input metadata.
408#[derive(Debug, Default, Clone, Serialize, Deserialize)]
409pub struct Options {
410    /// Preferred Media Type of the resolved DID document.
411    ///
412    /// [`accept`](https://www.w3.org/TR/did-spec-registries/#accept) resolution option.
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub accept: Option<representation::MediaType>,
415
416    /// DID parameters.
417    #[serde(flatten)]
418    pub parameters: Parameters,
419}
420
421impl Options {
422    pub fn extend(&mut self, other: Self) {
423        if let Some(value) = other.accept {
424            self.accept = Some(value)
425        }
426
427        self.parameters.extend(other.parameters)
428    }
429}
430
431/// DID parameters.
432///
433/// As specified in DID Core and/or in [DID Specification Registries][1].
434///
435/// [1]: https://www.w3.org/TR/did-spec-registries/#parameters
436#[derive(Debug, Default, Clone, Serialize, Deserialize)]
437#[serde(rename_all = "camelCase")]
438pub struct Parameters {
439    /// Service ID from the DID document.
440    #[serde(skip_serializing_if = "Option::is_none")]
441    pub service: Option<String>, // TODO must be an ASCII string.
442
443    /// Resource at a service endpoint, which is selected from a
444    /// DID document by using the service parameter.
445    #[serde(skip_serializing_if = "Option::is_none", alias = "relative-ref")]
446    pub relative_ref: Option<IriRefBuf>, // TODO must be an relative URI reference according to <https://www.rfc-editor.org/rfc/rfc3986#section-4.2>.
447
448    /// Specific version of a DID document to be resolved (the version ID could
449    /// be sequential, or a UUID, or method-specific).
450    #[serde(skip_serializing_if = "Option::is_none")]
451    pub version_id: Option<String>, // TODO must be an ASCII string.
452
453    /// Version timestamp of a DID document to be resolved. That is, the DID
454    /// document that was valid for a DID at a certain time.
455    #[serde(skip_serializing_if = "Option::is_none")]
456    pub version_time: Option<String>, // TODO must be an `xsd:string` literal value.
457
458    /// Resource hash of the DID document to add integrity protection, as
459    /// specified in [HASHLINK](https://www.w3.org/TR/did-core/#bib-hashlink).
460    ///
461    /// This parameter is non-normative.
462    #[serde(skip_serializing_if = "Option::is_none")]
463    pub hl: Option<String>, // TODO must be an ASCII string.
464
465    /// Expected public key format (non-standard option).
466    ///
467    /// Defined by <https://w3c-ccg.github.io/did-method-key>.
468    #[serde(skip_serializing_if = "Option::is_none")]
469    pub public_key_format: Option<String>,
470
471    /// Additional parameters.
472    #[serde(flatten)]
473    pub additional: BTreeMap<String, Parameter>,
474}
475
476impl Parameters {
477    pub fn extend(&mut self, other: Self) {
478        if let Some(value) = other.service {
479            self.service = Some(value)
480        }
481
482        if let Some(value) = other.relative_ref {
483            self.relative_ref = Some(value)
484        }
485
486        if let Some(value) = other.version_id {
487            self.version_id = Some(value)
488        }
489
490        if let Some(value) = other.version_time {
491            self.version_time = Some(value)
492        }
493
494        if let Some(value) = other.hl {
495            self.hl = Some(value)
496        }
497
498        if let Some(value) = other.public_key_format {
499            self.public_key_format = Some(value)
500        }
501
502        self.additional.extend(other.additional);
503    }
504}
505
506/// Arbitrary DID parameter.
507#[derive(Debug, Clone, Serialize, Deserialize)]
508#[serde(untagged)]
509pub enum Parameter {
510    Null,
511    String(String),
512    List(Vec<String>),
513}
514
515impl Parameter {
516    pub fn as_string(&self) -> Option<&str> {
517        match self {
518            Self::String(s) => Some(s),
519            _ => None,
520        }
521    }
522
523    pub fn into_string(self) -> Result<String, Self> {
524        match self {
525            Self::String(s) => Ok(s),
526            other => Err(other),
527        }
528    }
529}
530
531/// Resolution output metadata.
532#[derive(Debug, Default, Clone, Serialize)]
533#[serde(rename_all = "camelCase")]
534pub struct Metadata {
535    pub content_type: Option<String>,
536}
537
538impl Metadata {
539    pub fn from_content_type(content_type: Option<String>) -> Self {
540        Self { content_type }
541    }
542}