Skip to main content

identity_kit/did/
resolution.rs

1//! DID resolution types (DID Core §7.1, W3C DID Resolution): the result of resolving a DID, its
2//! two metadata structures, the resolution options and the error vocabulary.
3
4use core::fmt;
5use core::str::FromStr;
6
7use indexmap::IndexMap;
8use serde::de::Error as _;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use serde_json::Value;
11
12use super::{Did, DidDocument, DidError};
13
14/// Media type of the plain JSON representation of a DID document (DID Core §6.2).
15pub const APPLICATION_DID_JSON: &str = "application/did+json";
16/// Media type of the JSON-LD representation of a DID document (DID Core §6.3).
17pub const APPLICATION_DID_LD_JSON: &str = "application/did+ld+json";
18
19/// The error vocabulary of DID resolution (DID Core §7.1.2, DID Resolution).
20///
21/// Serializes as the registered keyword, for example `notFound`; a keyword this crate does not
22/// know is kept in [`Other`](Self::Other).
23#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
24#[non_exhaustive]
25pub enum ResolutionError {
26    /// `invalidDid`: the DID does not conform to the DID syntax.
27    #[error("invalid DID")]
28    InvalidDid,
29    /// `notFound`: no DID document exists for the DID.
30    #[error("DID not found")]
31    NotFound,
32    /// `methodNotSupported`: the resolver has no implementation of the DID method.
33    #[error("DID method not supported")]
34    MethodNotSupported,
35    /// `representationNotSupported`: the resolver cannot produce the requested media type.
36    #[error("representation not supported")]
37    RepresentationNotSupported,
38    /// `invalidDidDocument`: the method returned a document that is malformed or not the
39    /// document of the DID (DID Resolution "Errors").
40    #[error("invalid DID document")]
41    InvalidDidDocument,
42    /// `internalError`: the resolver failed unexpectedly (DID Resolution "Errors").
43    #[error("internal resolver error")]
44    InternalError,
45    /// Any other keyword.
46    #[error("resolution error `{0}`")]
47    Other(String),
48}
49
50impl ResolutionError {
51    /// The registered keyword, for example `notFound`.
52    pub fn as_str(&self) -> &str {
53        match self {
54            Self::InvalidDid => "invalidDid",
55            Self::NotFound => "notFound",
56            Self::MethodNotSupported => "methodNotSupported",
57            Self::RepresentationNotSupported => "representationNotSupported",
58            Self::InvalidDidDocument => "invalidDidDocument",
59            Self::InternalError => "internalError",
60            Self::Other(keyword) => keyword,
61        }
62    }
63}
64
65impl FromStr for ResolutionError {
66    type Err = core::convert::Infallible;
67
68    /// Never fails: an unknown keyword becomes [`ResolutionError::Other`].
69    fn from_str(s: &str) -> Result<Self, Self::Err> {
70        Ok(match s {
71            "invalidDid" => Self::InvalidDid,
72            "notFound" => Self::NotFound,
73            "methodNotSupported" => Self::MethodNotSupported,
74            "representationNotSupported" => Self::RepresentationNotSupported,
75            "invalidDidDocument" => Self::InvalidDidDocument,
76            "internalError" => Self::InternalError,
77            other => Self::Other(other.to_owned()),
78        })
79    }
80}
81
82impl From<DidError> for ResolutionError {
83    fn from(_: DidError) -> Self {
84        Self::InvalidDid
85    }
86}
87
88impl Serialize for ResolutionError {
89    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
90        serializer.serialize_str(self.as_str())
91    }
92}
93
94impl<'de> Deserialize<'de> for ResolutionError {
95    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
96        let keyword = String::deserialize(deserializer)?;
97        if keyword.is_empty() || !keyword.is_ascii() {
98            return Err(D::Error::custom("error must be an ASCII keyword"));
99        }
100        keyword.parse().map_err(|never| match never {})
101    }
102}
103
104/// Options passed to a resolver (DID Core §7.1.1 "DID resolution options").
105#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
106#[non_exhaustive]
107pub struct ResolutionOptions {
108    /// The media type the caller wants the document in.
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub accept: Option<String>,
111    /// Every other option, in insertion order.
112    #[serde(flatten)]
113    pub properties: IndexMap<String, Value>,
114}
115
116impl ResolutionOptions {
117    /// Options asking for `accept`.
118    pub fn accept(media_type: impl Into<String>) -> Self {
119        Self {
120            accept: Some(media_type.into()),
121            properties: IndexMap::new(),
122        }
123    }
124}
125
126/// Metadata about the resolution process (DID Core §7.1.3 "DID resolution metadata").
127#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
128#[non_exhaustive]
129pub struct ResolutionMetadata {
130    /// The media type of the returned document.
131    #[serde(
132        rename = "contentType",
133        default,
134        skip_serializing_if = "Option::is_none"
135    )]
136    pub content_type: Option<String>,
137    /// Why resolution failed.
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    pub error: Option<ResolutionError>,
140    /// Every other property, in insertion order.
141    #[serde(flatten)]
142    pub properties: IndexMap<String, Value>,
143}
144
145impl ResolutionMetadata {
146    /// Metadata reporting `error`.
147    pub fn error(error: ResolutionError) -> Self {
148        Self {
149            content_type: None,
150            error: Some(error),
151            properties: IndexMap::new(),
152        }
153    }
154
155    /// Metadata reporting `content_type`.
156    pub fn content_type(media_type: impl Into<String>) -> Self {
157        Self {
158            content_type: Some(media_type.into()),
159            error: None,
160            properties: IndexMap::new(),
161        }
162    }
163}
164
165/// Metadata about the DID document (DID Core §7.1.3 "DID document metadata").
166///
167/// Timestamps are XML Datetime strings in UTC without fractional seconds, as DID Core requires,
168/// for example `2020-12-20T19:17:47Z`; this layer carries them as strings.
169#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
170#[non_exhaustive]
171pub struct DocumentMetadata {
172    /// When the DID was created.
173    #[serde(default, skip_serializing_if = "Option::is_none")]
174    pub created: Option<String>,
175    /// When the resolved version was written.
176    #[serde(default, skip_serializing_if = "Option::is_none")]
177    pub updated: Option<String>,
178    /// Whether the DID has been deactivated.
179    #[serde(default, skip_serializing_if = "Option::is_none")]
180    pub deactivated: Option<bool>,
181    /// When the next version was written, if the resolved version is not the latest.
182    #[serde(
183        rename = "nextUpdate",
184        default,
185        skip_serializing_if = "Option::is_none"
186    )]
187    pub next_update: Option<String>,
188    /// The version of the resolved document.
189    #[serde(rename = "versionId", default, skip_serializing_if = "Option::is_none")]
190    pub version_id: Option<String>,
191    /// The version after the resolved one, if any.
192    #[serde(
193        rename = "nextVersionId",
194        default,
195        skip_serializing_if = "Option::is_none"
196    )]
197    pub next_version_id: Option<String>,
198    /// DIDs the method defines as logically equivalent to the resolved one.
199    #[serde(
200        rename = "equivalentId",
201        default,
202        skip_serializing_if = "Vec::is_empty"
203    )]
204    pub equivalent_id: Vec<Did>,
205    /// The DID the method defines as canonical for the resolved one.
206    #[serde(
207        rename = "canonicalId",
208        default,
209        skip_serializing_if = "Option::is_none"
210    )]
211    pub canonical_id: Option<Did>,
212    /// Every other property, in insertion order.
213    #[serde(flatten)]
214    pub properties: IndexMap<String, Value>,
215}
216
217impl DocumentMetadata {
218    /// Whether the DID is deactivated.
219    pub fn is_deactivated(&self) -> bool {
220        self.deactivated == Some(true)
221    }
222}
223
224/// What a resolver returns: the document when resolution succeeded, and the two metadata
225/// structures (DID Core §7.1.1).
226///
227/// # Examples
228///
229/// ```
230/// use identity_kit::did::{ResolutionError, ResolutionResult};
231///
232/// let failed = ResolutionResult::error(ResolutionError::NotFound);
233/// assert_eq!(
234///     serde_json::to_string(&failed)?,
235///     r#"{"didResolutionMetadata":{"error":"notFound"},"didDocumentMetadata":{}}"#
236/// );
237/// assert_eq!(failed.into_document(), Err(ResolutionError::NotFound));
238/// # Ok::<(), serde_json::Error>(())
239/// ```
240#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
241#[non_exhaustive]
242pub struct ResolutionResult {
243    /// The DID document, absent when resolution failed.
244    #[serde(
245        rename = "didDocument",
246        default,
247        skip_serializing_if = "Option::is_none"
248    )]
249    pub document: Option<DidDocument>,
250    /// Metadata about the resolution process.
251    #[serde(rename = "didResolutionMetadata", default)]
252    pub resolution_metadata: ResolutionMetadata,
253    /// Metadata about the document.
254    #[serde(rename = "didDocumentMetadata", default)]
255    pub document_metadata: DocumentMetadata,
256}
257
258impl ResolutionResult {
259    /// A successful result holding `document`.
260    pub fn new(document: DidDocument) -> Self {
261        Self {
262            document: Some(document),
263            resolution_metadata: ResolutionMetadata::default(),
264            document_metadata: DocumentMetadata::default(),
265        }
266    }
267
268    /// A failed result reporting `error`.
269    pub fn error(error: ResolutionError) -> Self {
270        Self {
271            document: None,
272            resolution_metadata: ResolutionMetadata::error(error),
273            document_metadata: DocumentMetadata::default(),
274        }
275    }
276
277    /// This result with `document_metadata`.
278    #[must_use]
279    pub fn with_document_metadata(mut self, metadata: DocumentMetadata) -> Self {
280        self.document_metadata = metadata;
281        self
282    }
283
284    /// This result with `resolution_metadata`.
285    #[must_use]
286    pub fn with_resolution_metadata(mut self, metadata: ResolutionMetadata) -> Self {
287        self.resolution_metadata = metadata;
288        self
289    }
290
291    /// The reported error, if any.
292    pub fn error_kind(&self) -> Option<&ResolutionError> {
293        self.resolution_metadata.error.as_ref()
294    }
295
296    /// The document, or the reported error; a result with neither counts as
297    /// [`ResolutionError::NotFound`].
298    ///
299    /// # Errors
300    ///
301    /// The [`ResolutionError`] of the resolution metadata.
302    pub fn into_document(self) -> Result<DidDocument, ResolutionError> {
303        match (self.document, self.resolution_metadata.error) {
304            (_, Some(error)) => Err(error),
305            (Some(document), None) => Ok(document),
306            (None, None) => Err(ResolutionError::NotFound),
307        }
308    }
309}
310
311impl fmt::Display for ResolutionResult {
312    /// The result as compact JSON, or a placeholder naming the reason when it cannot be
313    /// serialized (a verification method holding a private JWK).
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        match serde_json::to_string(self) {
316            Ok(json) => f.write_str(&json),
317            Err(e) => write!(f, "<unserializable resolution result: {e}>"),
318        }
319    }
320}