Skip to main content

did_url/
did.rs

1#[cfg(feature = "alloc")]
2use alloc::string::String;
3#[cfg(feature = "alloc")]
4use alloc::string::ToString as _;
5use core::cmp::Ordering;
6use core::convert::TryFrom;
7use core::fmt::Debug;
8use core::fmt::Display;
9use core::fmt::Formatter;
10use core::fmt::Result as FmtResult;
11use core::hash::Hash;
12use core::hash::Hasher;
13use core::str::FromStr;
14
15use crate::core::Core;
16use crate::error::Error;
17use crate::error::Result;
18
19#[derive(Clone, Copy)]
20pub struct Inspect<'a>(&'a DID);
21
22impl Debug for Inspect<'_> {
23  fn fmt(&self, f: &mut Formatter) -> FmtResult {
24    f.debug_struct("DID")
25      .field("method", &self.0.method())
26      .field("method_id", &self.0.method_id())
27      .field("path", &self.0.path())
28      .field("query", &self.0.query())
29      .field("fragment", &self.0.fragment())
30      .finish()
31  }
32}
33
34/// A Decentralized Identifier (DID).
35///
36/// [More Info (W3C DID Core)](https://www.w3.org/TR/did-core/)
37#[derive(Clone)]
38#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
39#[cfg_attr(feature = "serde", serde(into = "String", try_from = "String"))]
40pub struct DID {
41  data: String,
42  core: Core,
43}
44
45impl DID {
46  /// The URL scheme for Decentralized Identifiers.
47  pub const SCHEME: &'static str = "did";
48
49  /// Parses a [`DID`] from the provided `input`.
50  ///
51  /// # Errors
52  ///
53  /// Returns `Err` if any DID segments are invalid.
54  pub fn parse(input: impl AsRef<str>) -> Result<Self> {
55    Ok(Self {
56      data: input.as_ref().to_string(),
57      core: Core::parse(input)?,
58    })
59  }
60
61  /// Returns a wrapped `DID` with a more detailed `Debug` implementation.
62  #[inline]
63  pub const fn inspect(&self) -> Inspect {
64    Inspect(self)
65  }
66
67  /// Returns the serialized [`DID`].
68  ///
69  /// This is fast since the serialized value is stored in the [`DID`].
70  #[inline]
71  pub fn as_str(&self) -> &str {
72    &*self.data
73  }
74
75  /// Consumes the [`DID`] and returns the serialization.
76  #[cfg(feature = "alloc")]
77  #[inline]
78  pub fn into_string(self) -> String {
79    self.data
80  }
81
82  /// Returns the [`DID`] scheme. See [`DID::SCHEME`].
83  #[inline]
84  pub const fn scheme(&self) -> &'static str {
85    DID::SCHEME
86  }
87
88  /// Returns the [`DID`] authority.
89  #[inline]
90  pub fn authority(&self) -> &str {
91    self.core.authority(self.as_str())
92  }
93
94  /// Returns the [`DID`] method name.
95  #[inline]
96  pub fn method(&self) -> &str {
97    self.core.method(self.as_str())
98  }
99
100  /// Returns the [`DID`] method-specific ID.
101  #[inline]
102  pub fn method_id(&self) -> &str {
103    self.core.method_id(self.as_str())
104  }
105
106  /// Returns the [`DID`] path.
107  #[inline]
108  pub fn path(&self) -> &str {
109    self.core.path(self.as_str())
110  }
111
112  /// Returns the [`DID`] method query, if any.
113  #[inline]
114  pub fn query(&self) -> Option<&str> {
115    self.core.query(self.as_str())
116  }
117
118  /// Returns the [`DID`] method fragment, if any.
119  #[inline]
120  pub fn fragment(&self) -> Option<&str> {
121    self.core.fragment(self.as_str())
122  }
123
124  /// Parses the [`DID`] query and returns an iterator of (key, value) pairs.
125  #[inline]
126  pub fn query_pairs(&self) -> form_urlencoded::Parse {
127    self.core.query_pairs(self.as_str())
128  }
129
130  /// Change the method of the [`DID`].
131  #[inline]
132  pub fn set_method(&mut self, value: impl AsRef<str>) {
133    self.core.set_method(&mut self.data, value.as_ref());
134  }
135
136  /// Change the method-specific-id of the [`DID`].
137  #[inline]
138  pub fn set_method_id(&mut self, value: impl AsRef<str>) {
139    self.core.set_method_id(&mut self.data, value.as_ref());
140  }
141
142  /// Change the path of the [`DID`].
143  #[inline]
144  pub fn set_path(&mut self, value: impl AsRef<str>) {
145    self.core.set_path(&mut self.data, value.as_ref());
146  }
147
148  /// Change the query of the [`DID`].
149  ///
150  /// No serialization is performed.
151  #[inline]
152  pub fn set_query(&mut self, value: Option<&str>) {
153    self.core.set_query(&mut self.data, value);
154  }
155
156  /// Change the fragment of the [`DID`].
157  ///
158  /// No serialization is performed.
159  #[inline]
160  pub fn set_fragment(&mut self, value: Option<&str>) {
161    self.core.set_fragment(&mut self.data, value);
162  }
163
164  /// Creates a new [`DID`] by joining `self` with the relative DID `other`.
165  ///
166  /// # Errors
167  ///
168  /// Returns `Err` if any base or relative DID segments are invalid.
169  #[cfg(feature = "alloc")]
170  pub fn join(&self, other: impl AsRef<str>) -> Result<Self> {
171    let data: &str = other.as_ref();
172    let core: Core = Core::parse_relative(data)?;
173
174    resolution::transform_references(self, (data, &core))
175  }
176}
177
178impl Hash for DID {
179  fn hash<H>(&self, hasher: &mut H)
180  where
181    H: Hasher,
182  {
183    self.as_str().hash(hasher)
184  }
185}
186
187impl PartialEq for DID {
188  fn eq(&self, other: &Self) -> bool {
189    self.as_str() == other.as_str()
190  }
191}
192
193impl Eq for DID {}
194
195impl PartialOrd for DID {
196  fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
197    self.as_str().partial_cmp(other.as_str())
198  }
199}
200
201impl Ord for DID {
202  fn cmp(&self, other: &Self) -> Ordering {
203    self.as_str().cmp(other.as_str())
204  }
205}
206
207impl PartialEq<str> for DID {
208  fn eq(&self, other: &str) -> bool {
209    self.as_str() == other
210  }
211}
212
213impl PartialEq<&'_ str> for DID {
214  fn eq(&self, other: &&'_ str) -> bool {
215    self == *other
216  }
217}
218
219impl Debug for DID {
220  fn fmt(&self, f: &mut Formatter) -> FmtResult {
221    f.write_fmt(format_args!("{:?}", self.as_str()))
222  }
223}
224
225impl Display for DID {
226  fn fmt(&self, f: &mut Formatter) -> FmtResult {
227    f.write_fmt(format_args!("{}", self.as_str()))
228  }
229}
230
231impl AsRef<str> for DID {
232  fn as_ref(&self) -> &str {
233    self.data.as_ref()
234  }
235}
236
237impl FromStr for DID {
238  type Err = Error;
239
240  fn from_str(string: &str) -> Result<Self, Self::Err> {
241    Self::parse(string)
242  }
243}
244
245#[cfg(feature = "alloc")]
246impl TryFrom<String> for DID {
247  type Error = Error;
248
249  fn try_from(other: String) -> Result<Self, Self::Error> {
250    Self::parse(other)
251  }
252}
253
254#[cfg(feature = "alloc")]
255impl From<DID> for String {
256  fn from(other: DID) -> Self {
257    other.into_string()
258  }
259}
260
261// =============================================================================
262// Reference Resolution
263// See RFC 3986 - https://tools.ietf.org/html/rfc3986#section-5
264// =============================================================================
265
266#[cfg(feature = "alloc")]
267mod resolution {
268  use alloc::borrow::Cow;
269  use core::fmt::Display;
270  use core::fmt::Formatter;
271  use core::fmt::Result as FmtResult;
272  use core::str::from_utf8_unchecked;
273
274  use crate::core::Core;
275  use crate::did::DID;
276  use crate::error::Error;
277  use crate::error::Result;
278
279  #[derive(Debug)]
280  #[repr(transparent)]
281  pub struct Path<'a>(Cow<'a, str>);
282
283  impl<'a> Path<'a> {
284    pub const fn new() -> Self {
285      Self(Cow::Borrowed(""))
286    }
287
288    pub fn push(&mut self, value: impl AsRef<[u8]>) {
289      self
290        .0
291        .to_mut()
292        .push_str(unsafe { from_utf8_unchecked(value.as_ref()) });
293    }
294
295    pub fn pop(&mut self) {
296      if self.0.is_empty() {
297        return;
298      }
299
300      if let Some(index) = self.0.rfind('/') {
301        self.0.to_mut().replace_range(index.., "");
302      }
303    }
304  }
305
306  impl<'a> From<Path<'a>> for Cow<'a, str> {
307    fn from(other: Path<'a>) -> Self {
308      other.0
309    }
310  }
311
312  impl Display for Path<'_> {
313    fn fmt(&self, f: &mut Formatter) -> FmtResult {
314      Display::fmt(&self.0, f)
315    }
316  }
317
318  /// Transform References.
319  ///
320  /// Transforms a DID reference into its target DID.
321  ///
322  /// [More Info](https://tools.ietf.org/html/rfc3986#section-5.2.2)
323  #[allow(non_snake_case)]
324  pub fn transform_references(base: &DID, (data, core): (&str, &Core)) -> Result<DID> {
325    let P: &str = core.path(data);
326    let Q: Option<&str> = core.query(data);
327
328    let mut T: DID = base.clone();
329
330    if P.is_empty() {
331      T.set_path(base.path());
332      T.set_query(Q.or_else(|| base.query()));
333    } else {
334      if P.starts_with('/') {
335        T.set_path(remove_dot_segments(P));
336      } else {
337        T.set_path(remove_dot_segments(&merge_paths(base, P)?));
338      }
339
340      T.set_query(Q);
341    }
342
343    T.set_method(base.method()); // TODO: Remove? This in inherited via clone
344    T.set_method_id(base.method_id()); // TODO: Remove? This in inherited via clone
345    T.set_fragment(core.fragment(data));
346
347    Ok(T)
348  }
349
350  /// Merge Paths.
351  ///
352  /// Merges a relative-path reference with the path of the base DID.
353  ///
354  /// [More Info](https://tools.ietf.org/html/rfc3986#section-5.2.3)
355  pub fn merge_paths<'a>(base: &'a DID, data: &'a str) -> Result<Cow<'a, str>> {
356    // Ensure the base DID has an authority component.
357    //
358    // The DID authority is `<method>:<method-specific-id>` so it should always
359    // be present for non-relative DIDs.
360    if base.method().is_empty() || base.method_id().is_empty() {
361      return Err(Error::InvalidAuthority);
362    }
363
364    // 1. If the base URI has a defined authority component and an empty
365    // path, then return a string consisting of "/" concatenated with the
366    // reference's path.
367
368    if base.path().is_empty() {
369      return Ok(data.into());
370    }
371
372    // 2. Return a string consisting of the reference's path component
373    // appended to all but the last segment of the base URI's path (i.e.,
374    // excluding any characters after the right-most "/" in the base URI
375    // path, or excluding the entire base URI path if it does not contain
376    // any "/" characters).
377
378    let mut path: &str = base.path();
379
380    if let Some(index) = path.rfind('/') {
381      path = &path[..=index];
382    }
383
384    Ok([path, data].join("").into())
385  }
386
387  /// Remove Dot Segments.
388  ///
389  /// [More Info](https://tools.ietf.org/html/rfc3986#section-5.2.4)
390  pub fn remove_dot_segments(path: &str) -> Cow<str> {
391    fn next_segment(input: impl AsRef<[u8]>) -> Option<usize> {
392      match input.as_ref() {
393        [b'/', input @ ..] => next_segment(input).map(|index| index + 1),
394        input => input.iter().position(|byte| *byte == b'/'),
395      }
396    }
397
398    let mut output: Path = Path::new();
399    let mut input: &[u8] = path.as_bytes();
400
401    loop {
402      match input {
403        // Remove prefix ../
404        [b'.', b'.', b'/', ..] => {
405          input = &input[3..];
406        }
407        // Remove prefix ./
408        [b'.', b'/', ..] => {
409          input = &input[2..];
410        }
411        // Replace prefix /./
412        [b'/', b'.', b'/', ..] => {
413          input = &input[2..];
414        }
415        // Replace prefix /.
416        [b'/', b'.'] => {
417          input = &input[..1];
418        }
419        // Replace prefix /../
420        [b'/', b'.', b'.', b'/', ..] => {
421          input = &input[3..];
422          output.pop();
423        }
424        // Replace prefix /..
425        [b'/', b'.', b'.'] => {
426          input = &input[..2];
427          output.pop();
428        }
429        // Remove .
430        [b'.'] => {
431          input = &input[1..];
432        }
433        // Remove ..
434        [b'.', b'.'] => {
435          input = &input[2..];
436        }
437        _ => {
438          if let Some(index) = next_segment(input) {
439            output.push(&input[..index]);
440            input = &input[index..];
441          } else {
442            output.push(input);
443            break;
444          }
445        }
446      }
447    }
448
449    output.into()
450  }
451}