identity_did/
did.rs

1// Copyright 2020-2023 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use core::convert::TryFrom;
5use core::fmt::Debug;
6use core::fmt::Display;
7use core::fmt::Formatter;
8use core::str::FromStr;
9use std::hash::Hash;
10
11use did_url_parser::DID as BaseDIDUrl;
12
13use identity_core::common::KeyComparable;
14
15use crate::DIDUrl;
16use crate::Error;
17
18pub trait DID:
19  Clone
20  + PartialEq
21  + Eq
22  + PartialOrd
23  + Ord
24  + Hash
25  + FromStr
26  + TryFrom<CoreDID>
27  + Into<String>
28  + Into<CoreDID>
29  + AsRef<CoreDID>
30{
31  const SCHEME: &'static str = BaseDIDUrl::SCHEME;
32
33  /// Returns the [`DID`] scheme. See [`DID::SCHEME`].
34  ///
35  /// E.g.
36  /// - `"did:example:12345678" -> "did"`
37  /// - `"did:iota:main:12345678" -> "did"`
38  fn scheme(&self) -> &'static str {
39    self.as_ref().0.scheme()
40  }
41
42  /// Returns the [`DID`] authority: the method name and method-id.
43  ///
44  /// E.g.
45  /// - `"did:example:12345678" -> "example:12345678"`
46  /// - `"did:iota:main:12345678" -> "iota:main:12345678"`
47  fn authority(&self) -> &str {
48    self.as_ref().0.authority()
49  }
50
51  /// Returns the [`DID`] method name.
52  ///
53  /// E.g.
54  /// - `"did:example:12345678" -> "example"`
55  /// - `"did:iota:main:12345678" -> "iota"`
56  fn method(&self) -> &str {
57    self.as_ref().0.method()
58  }
59
60  /// Returns the [`DID`] method-specific ID.
61  ///
62  /// E.g.
63  /// - `"did:example:12345678" -> "12345678"`
64  /// - `"did:iota:main:12345678" -> "main:12345678"`
65  fn method_id(&self) -> &str {
66    self.as_ref().0.method_id()
67  }
68
69  /// Returns the serialized [`DID`].
70  ///
71  /// This is fast since the serialized value is stored in the [`DID`].
72  fn as_str(&self) -> &str {
73    self.as_ref().0.as_str()
74  }
75
76  /// Consumes the [`DID`] and returns its serialization.
77  fn into_string(self) -> String {
78    self.into()
79  }
80
81  /// Constructs a [`DIDUrl`] by attempting to append a string representing a path, query, and/or
82  /// fragment to this [`DID`].
83  ///
84  /// See [`DIDUrl::join`].
85  fn join(self, value: impl AsRef<str>) -> Result<DIDUrl, Error> {
86    let url = DIDUrl::from(self);
87    url.join(value)
88  }
89
90  /// Clones the [`DID`] into a [`DIDUrl`] of the same method.
91  fn to_url(&self) -> DIDUrl {
92    DIDUrl::new(self.clone().into(), None)
93  }
94
95  /// Converts the [`DID`] into a [`DIDUrl`] of the same method.
96  fn into_url(self) -> DIDUrl {
97    DIDUrl::from(self)
98  }
99}
100
101#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
102#[repr(transparent)]
103#[serde(into = "BaseDIDUrl", try_from = "BaseDIDUrl")]
104/// A wrapper around [`BaseDIDUrl`](BaseDIDUrl).
105pub struct CoreDID(BaseDIDUrl);
106
107impl CoreDID {
108  /// Parses a [`CoreDID`] from the given `input`.
109  ///
110  /// # Errors
111  ///
112  /// Returns `Err` if the input is not a valid [`DID`].
113  pub fn parse(input: impl AsRef<str>) -> Result<Self, Error> {
114    BaseDIDUrl::parse(input).map(Self).map_err(Error::from)
115  }
116
117  /// Set the method name of the [`DID`].
118  pub fn set_method_name(&mut self, value: impl AsRef<str>) -> Result<(), Error> {
119    Self::valid_method_name(value.as_ref())?;
120    self.0.set_method(value);
121    Ok(())
122  }
123
124  /// Validates whether a string is a valid [`DID`] method name.
125  pub fn valid_method_name(value: &str) -> Result<(), Error> {
126    if !value.chars().all(is_char_method_name) {
127      return Err(Error::InvalidMethodName);
128    }
129    Ok(())
130  }
131
132  /// Set the method-specific-id of the [`DID`].
133  pub fn set_method_id(&mut self, value: impl AsRef<str>) -> Result<(), Error> {
134    Self::valid_method_id(value.as_ref())?;
135    self.0.set_method_id(value);
136    Ok(())
137  }
138
139  /// Validates whether a string is a valid [`DID`] method-id.
140  pub fn valid_method_id(value: &str) -> Result<(), Error> {
141    // if !value.chars().all(is_char_method_id) {
142    //   return Err(Error::InvalidMethodId);
143    // }
144    let mut chars = value.chars();
145    while let Some(c) = chars.next() {
146      match c {
147        '%' => {
148          let digits = chars.clone().take(2).collect::<String>();
149          u8::from_str_radix(&digits, 16).map_err(|_| Error::InvalidMethodId)?;
150          chars.next();
151          chars.next();
152        }
153        c if is_char_method_id(c) => (),
154        _ => return Err(Error::InvalidMethodId),
155      }
156    }
157
158    Ok(())
159  }
160
161  /// Checks if the given `did` is valid according to the base [`DID`] specification.
162  pub fn check_validity(did: &BaseDIDUrl) -> Result<(), Error> {
163    // Validate basic DID constraints.
164    Self::valid_method_name(did.method())?;
165    Self::valid_method_id(did.method_id())?;
166    if did.scheme() != Self::SCHEME {
167      return Err(Error::InvalidScheme);
168    }
169
170    // Ensure no DID Url segments are present.
171    if !did.path().is_empty() || did.fragment().is_some() || did.query().is_some() {
172      return Err(Error::InvalidMethodId);
173    }
174
175    Ok(())
176  }
177}
178
179impl AsRef<CoreDID> for CoreDID {
180  fn as_ref(&self) -> &CoreDID {
181    self
182  }
183}
184
185impl From<CoreDID> for BaseDIDUrl {
186  fn from(did: CoreDID) -> Self {
187    did.0
188  }
189}
190
191impl TryFrom<BaseDIDUrl> for CoreDID {
192  type Error = Error;
193
194  fn try_from(base_did_url: BaseDIDUrl) -> Result<Self, Self::Error> {
195    Ok(Self(base_did_url))
196  }
197}
198
199impl Debug for CoreDID {
200  fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
201    f.write_fmt(format_args!("{}", self.as_str()))
202  }
203}
204
205impl Display for CoreDID {
206  fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
207    f.write_fmt(format_args!("{}", self.as_str()))
208  }
209}
210
211impl AsRef<str> for CoreDID {
212  fn as_ref(&self) -> &str {
213    self.0.as_ref()
214  }
215}
216
217impl FromStr for CoreDID {
218  type Err = Error;
219
220  fn from_str(string: &str) -> Result<Self, Self::Err> {
221    Self::parse(string)
222  }
223}
224
225impl TryFrom<&str> for CoreDID {
226  type Error = Error;
227
228  fn try_from(other: &str) -> Result<Self, Self::Error> {
229    Self::parse(other)
230  }
231}
232
233impl TryFrom<String> for CoreDID {
234  type Error = Error;
235
236  fn try_from(other: String) -> Result<Self, Self::Error> {
237    Self::parse(other)
238  }
239}
240
241impl From<CoreDID> for String {
242  fn from(did: CoreDID) -> Self {
243    did.0.into_string()
244  }
245}
246
247impl PartialEq<str> for CoreDID {
248  fn eq(&self, other: &str) -> bool {
249    self.as_str() == other
250  }
251}
252
253impl PartialEq<&'_ str> for CoreDID {
254  fn eq(&self, other: &&'_ str) -> bool {
255    self == *other
256  }
257}
258
259impl KeyComparable for CoreDID {
260  type Key = CoreDID;
261
262  #[inline]
263  fn key(&self) -> &Self::Key {
264    self
265  }
266}
267
268/// Checks whether a character satisfies DID method name constraints:
269/// { 0-9 | a-z }
270#[inline(always)]
271pub(crate) const fn is_char_method_name(ch: char) -> bool {
272  matches!(ch, '0'..='9' | 'a'..='z')
273}
274
275/// Checks whether a character satisfies DID method-id constraints:
276/// { 0-9 | a-z | A-Z | . | - | _ | : }
277#[inline(always)]
278pub(crate) const fn is_char_method_id(ch: char) -> bool {
279  matches!(ch, '0'..='9' | 'a'..='z' | 'A'..='Z' | '.' | '-' | '_' | ':')
280}
281
282impl<D> DID for D where
283  D: Clone
284    + PartialEq
285    + Eq
286    + PartialOrd
287    + Ord
288    + Hash
289    + FromStr
290    + TryFrom<CoreDID>
291    + Into<String>
292    + Into<CoreDID>
293    + AsRef<CoreDID>
294{
295}
296
297#[cfg(test)]
298mod tests {
299  use super::*;
300
301  #[test]
302  fn test_core_did_valid() {
303    assert_eq!(
304      CoreDID::parse("did:example:123456890").unwrap(),
305      "did:example:123456890"
306    );
307    assert_eq!(
308      CoreDID::parse("did:iota:main:123456890").unwrap(),
309      "did:iota:main:123456890"
310    );
311  }
312
313  #[test]
314  fn test_core_did_invalid() {
315    assert!(CoreDID::parse("").is_err());
316    assert!(CoreDID::parse("did:").is_err());
317    assert!(CoreDID::parse("dad:example:123456890").is_err());
318  }
319
320  proptest::proptest! {
321    #[test]
322    fn test_fuzz_core_did_valid(s in r"did:[a-z0-9]{1,10}:[a-zA-Z0-9\.\-_:]{1,60}") {
323      assert_eq!(CoreDID::parse(&s).unwrap().as_str(), &s);
324    }
325
326    #[test]
327    fn test_fuzz_core_did_no_panic(s in "\\PC*") {
328      assert!(CoreDID::parse(s).is_err());
329    }
330
331    #[test]
332    fn test_fuzz_set_method_name_no_panic(s in "\\PC*") {
333      let mut did = CoreDID::parse("did:example:1234567890").unwrap();
334      let _ = did.set_method_id(&s);
335    }
336
337    #[test]
338    fn test_fuzz_set_method_id_no_panic(s in "\\PC*") {
339      let mut did = CoreDID::parse("did:example:1234567890").unwrap();
340      let _ = did.set_method_name(&s);
341    }
342  }
343}