Skip to main content

identity_kit/methods/
mod.rs

1//! DID methods: `did:key` ([`key`]), `did:jwk` ([`jwk`]) and `did:web` ([`web`]).
2//!
3//! Each method is a type implementing [`DidResolver`] and [`DidProducer`], registered with a
4//! [`Resolver`] through [`ResolverBuilder::with`]; [`default_methods`] registers all three at
5//! once. The methods are behind the `did-key`, `did-jwk` and `did-web` Cargo features, all on
6//! by default; `did:web` needs an [`HttpClient`](web::HttpClient), and the `reqwest` feature
7//! provides [`ReqwestClient`](web::ReqwestClient), a hardened default.
8//!
9//! # Examples
10//!
11//! ```
12//! # #[cfg(all(feature = "did-key", feature = "did-jwk", feature = "reqwest"))]
13//! # tokio::runtime::Builder::new_current_thread().enable_all().build()?.block_on(async {
14//! use identity_kit::did::DidUrl;
15//! use identity_kit::methods::web::ReqwestClient;
16//!
17//! let resolver = identity_kit::methods::default_methods(ReqwestClient::new()?).build();
18//! assert_eq!(resolver.methods(), ["jwk", "key", "web"]);
19//!
20//! // Embedded methods need no network: the document is derived from the DID.
21//! let key = DidUrl::parse(
22//!     "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
23//! )?;
24//! let jwk = resolver.resolve_public_key(&key, None).await?;
25//! assert_eq!(jwk.crv(), Some("Ed25519"));
26//! # Ok::<(), Box<dyn std::error::Error>>(())
27//! # })?;
28//! # Ok::<(), Box<dyn std::error::Error>>(())
29//! ```
30
31#[cfg(feature = "did-jwk")]
32#[cfg_attr(docsrs, doc(cfg(feature = "did-jwk")))]
33pub mod jwk;
34#[cfg(feature = "did-key")]
35#[cfg_attr(docsrs, doc(cfg(feature = "did-key")))]
36pub mod key;
37#[cfg(feature = "did-web")]
38#[cfg_attr(docsrs, doc(cfg(feature = "did-web")))]
39pub mod web;
40
41#[cfg(feature = "did-jwk")]
42mod jcs;
43
44#[cfg(feature = "did-jwk")]
45pub use jwk::DidJwk;
46#[cfg(feature = "did-key")]
47pub use key::DidKey;
48#[cfg(feature = "did-web")]
49pub use web::DidWeb;
50
51use crate::did::{
52    APPLICATION_DID_JSON, APPLICATION_DID_LD_JSON, ResolutionError, ResolutionErrorKind,
53    ResolutionOptions,
54};
55#[cfg(any(feature = "did-key", feature = "did-jwk"))]
56use crate::jose::Jwk;
57#[cfg(any(feature = "did-key", feature = "did-jwk"))]
58use crate::resolver::ProduceError;
59#[allow(unused_imports)]
60use crate::resolver::{DidProducer, DidResolver, Resolver, ResolverBuilder};
61
62/// A [`ResolverBuilder`] with every method this crate ships registered, with their defaults:
63/// `did:key` ([`DidKey`]), `did:jwk` ([`DidJwk`]) and `did:web` over `http`.
64///
65/// Register a configured method afterwards to replace a default, and add a cache with
66/// [`ResolverBuilder::cache`] before [`build`](ResolverBuilder::build).
67#[cfg(feature = "did-web")]
68#[cfg_attr(docsrs, doc(cfg(feature = "did-web")))]
69pub fn default_methods(http: impl web::HttpClient + 'static) -> ResolverBuilder {
70    let builder = Resolver::builder();
71    #[cfg(feature = "did-key")]
72    let builder = builder.with(DidKey::default());
73    #[cfg(feature = "did-jwk")]
74    let builder = builder.with(DidJwk);
75    builder.with(DidWeb::new(http))
76}
77
78/// The one key an embedded method takes.
79#[cfg(any(feature = "did-key", feature = "did-jwk"))]
80fn single_key(keys: &[Jwk]) -> Result<&Jwk, ProduceError> {
81    match keys {
82        [] => Err(ProduceError::NoKeys),
83        [key] => Ok(key),
84        _ => Err(ProduceError::TooManyKeys {
85            max: 1,
86            actual: keys.len(),
87        }),
88    }
89}
90
91/// The media types a method of this crate can serve, which are all the JSON ones.
92const JSON_MEDIA_TYPES: [&str; 3] = [
93    APPLICATION_DID_JSON,
94    APPLICATION_DID_LD_JSON,
95    "application/json",
96];
97
98/// Refuses an `accept` option asking for anything but a JSON representation.
99fn check_accept(options: &ResolutionOptions) -> Result<(), ResolutionError> {
100    match options.accept.as_deref() {
101        None => Ok(()),
102        Some(accept) if JSON_MEDIA_TYPES.contains(&accept) => Ok(()),
103        Some(accept) => Err(
104            ResolutionError::new(ResolutionErrorKind::RepresentationNotSupported)
105                .with_detail(format!("`{accept}` is not a JSON representation")),
106        ),
107    }
108}