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    let base_did_url = BaseDIDUrl::parse(input)?;
115    base_did_url.try_into()
116  }
117
118  /// Set the method name of the [`DID`].
119  pub fn set_method_name(&mut self, value: impl AsRef<str>) -> Result<(), Error> {
120    Self::valid_method_name(value.as_ref())?;
121    self.0.set_method(value);
122    Ok(())
123  }
124
125  /// Validates whether a string is a valid [`DID`] method name.
126  pub fn valid_method_name(value: &str) -> Result<(), Error> {
127    if !value.chars().all(is_char_method_name) {
128      return Err(Error::InvalidMethodName);
129    }
130    Ok(())
131  }
132
133  /// Set the method-specific-id of the [`DID`].
134  pub fn set_method_id(&mut self, value: impl AsRef<str>) -> Result<(), Error> {
135    Self::valid_method_id(value.as_ref())?;
136    self.0.set_method_id(value);
137    Ok(())
138  }
139
140  /// Validates whether a string is a valid [`DID`] method-id.
141  pub fn valid_method_id(value: &str) -> Result<(), Error> {
142    // if !value.chars().all(is_char_method_id) {
143    //   return Err(Error::InvalidMethodId);
144    // }
145    let mut chars = value.chars();
146    while let Some(c) = chars.next() {
147      match c {
148        '%' => {
149          let digits = chars.clone().take(2).collect::<String>();
150          u8::from_str_radix(&digits, 16).map_err(|_| Error::InvalidMethodId)?;
151          chars.next();
152          chars.next();
153        }
154        c if is_char_method_id(c) => (),
155        _ => return Err(Error::InvalidMethodId),
156      }
157    }
158
159    Ok(())
160  }
161
162  /// Checks if the given `did` is valid according to the base [`DID`] specification.
163  pub fn check_validity(did: &BaseDIDUrl) -> Result<(), Error> {
164    // Validate basic DID constraints.
165    Self::valid_method_name(did.method())?;
166    Self::valid_method_id(did.method_id())?;
167    if did.scheme() != Self::SCHEME {
168      return Err(Error::InvalidScheme);
169    }
170
171    // Ensure no DID Url segments are present.
172    if !did.path().is_empty() || did.fragment().is_some() || did.query().is_some() {
173      return Err(Error::InvalidMethodId);
174    }
175
176    Ok(())
177  }
178}
179
180impl AsRef<CoreDID> for CoreDID {
181  fn as_ref(&self) -> &CoreDID {
182    self
183  }
184}
185
186impl From<CoreDID> for BaseDIDUrl {
187  fn from(did: CoreDID) -> Self {
188    did.0
189  }
190}
191
192impl TryFrom<BaseDIDUrl> for CoreDID {
193  type Error = Error;
194
195  fn try_from(base_did_url: BaseDIDUrl) -> Result<Self, Self::Error> {
196    Self::check_validity(&base_did_url)?;
197    Ok(Self(base_did_url))
198  }
199}
200
201impl Debug for CoreDID {
202  fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
203    f.write_fmt(format_args!("{}", self.as_str()))
204  }
205}
206
207impl Display for CoreDID {
208  fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
209    f.write_fmt(format_args!("{}", self.as_str()))
210  }
211}
212
213impl AsRef<str> for CoreDID {
214  fn as_ref(&self) -> &str {
215    self.0.as_ref()
216  }
217}
218
219impl FromStr for CoreDID {
220  type Err = Error;
221
222  fn from_str(string: &str) -> Result<Self, Self::Err> {
223    Self::parse(string)
224  }
225}
226
227impl TryFrom<&str> for CoreDID {
228  type Error = Error;
229
230  fn try_from(other: &str) -> Result<Self, Self::Error> {
231    Self::parse(other)
232  }
233}
234
235impl TryFrom<String> for CoreDID {
236  type Error = Error;
237
238  fn try_from(other: String) -> Result<Self, Self::Error> {
239    Self::parse(other)
240  }
241}
242
243impl From<CoreDID> for String {
244  fn from(did: CoreDID) -> Self {
245    did.0.into_string()
246  }
247}
248
249impl PartialEq<str> for CoreDID {
250  fn eq(&self, other: &str) -> bool {
251    self.as_str() == other
252  }
253}
254
255impl PartialEq<&'_ str> for CoreDID {
256  fn eq(&self, other: &&'_ str) -> bool {
257    self == *other
258  }
259}
260
261impl KeyComparable for CoreDID {
262  type Key = CoreDID;
263
264  #[inline]
265  fn key(&self) -> &Self::Key {
266    self
267  }
268}
269
270/// Checks whether a character satisfies DID method name constraints:
271/// { 0-9 | a-z }
272#[inline(always)]
273pub(crate) const fn is_char_method_name(ch: char) -> bool {
274  matches!(ch, '0'..='9' | 'a'..='z')
275}
276
277/// Checks whether a character satisfies DID method-id constraints:
278/// { 0-9 | a-z | A-Z | . | - | _ | : }
279#[inline(always)]
280pub(crate) const fn is_char_method_id(ch: char) -> bool {
281  matches!(ch, '0'..='9' | 'a'..='z' | 'A'..='Z' | '.' | '-' | '_' | ':')
282}
283
284impl<D> DID for D where
285  D: Clone
286    + PartialEq
287    + Eq
288    + PartialOrd
289    + Ord
290    + Hash
291    + FromStr
292    + TryFrom<CoreDID>
293    + Into<String>
294    + Into<CoreDID>
295    + AsRef<CoreDID>
296{
297}
298
299#[cfg(test)]
300mod tests {
301  use super::*;
302
303  #[test]
304  fn test_core_did_valid() {
305    assert_eq!(
306      CoreDID::parse("did:example:123456890").unwrap(),
307      "did:example:123456890"
308    );
309    assert_eq!(
310      CoreDID::parse("did:iota:main:123456890").unwrap(),
311      "did:iota:main:123456890"
312    );
313  }
314
315  #[test]
316  fn test_core_did_invalid() {
317    assert!(CoreDID::parse("").is_err());
318    assert!(CoreDID::parse("did:").is_err());
319    assert!(CoreDID::parse("dad:example:123456890").is_err());
320    assert!(CoreDID::parse("did:example:123456#key-1").is_err());
321    assert!(CoreDID::parse("did:example:123456/path/to/something/0").is_err());
322    assert!(CoreDID::parse("did:example:123456?key-id=cool-key").is_err());
323  }
324
325  proptest::proptest! {
326    #[test]
327    fn test_fuzz_core_did_valid(s in r"did:[a-z0-9]{1,10}:[a-zA-Z0-9\.\-_:]{1,60}") {
328      assert_eq!(CoreDID::parse(&s).unwrap().as_str(), &s);
329    }
330
331    #[test]
332    fn test_fuzz_core_did_no_panic(s in "\\PC*") {
333      assert!(CoreDID::parse(s).is_err());
334    }
335
336    #[test]
337    fn test_fuzz_set_method_name_no_panic(s in "\\PC*") {
338      let mut did = CoreDID::parse("did:example:1234567890").unwrap();
339      let _ = did.set_method_id(&s);
340    }
341
342    #[test]
343    fn test_fuzz_set_method_id_no_panic(s in "\\PC*") {
344      let mut did = CoreDID::parse("did:example:1234567890").unwrap();
345      let _ = did.set_method_name(&s);
346    }
347  }
348}