Skip to main content

identity_kit/did/
reference.rs

1//! `DidUrlReference`: an absolute DID URL or a relative reference to one.
2
3use core::fmt;
4use core::str::FromStr;
5
6use serde::de::Error as _;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9use super::syntax::{check_len, scan_url_suffix};
10use super::{DEFAULT_MAX_LEN, Did, DidError, DidUrl};
11
12/// A reference to a resource of a DID document: either an absolute [`DidUrl`] or a relative DID
13/// URL (DID Core §3.2.2) such as `#key-1`, which is resolved against the document's `id`.
14///
15/// Verification relationships reference verification methods this way, and a document may use
16/// either form, so the reference keeps whichever it was given and [`resolve`](Self::resolve)
17/// turns it into an absolute DID URL when a base DID is known.
18///
19/// A relative reference is a path, query and fragment (any of them, in that order), so it starts
20/// with `/`, `?` or `#`.
21///
22/// # Examples
23///
24/// ```
25/// use identity_kit::did::{Did, DidUrlReference};
26///
27/// let base: Did = "did:example:123".parse()?;
28///
29/// let relative = DidUrlReference::parse("#key-1")?;
30/// assert!(relative.is_relative());
31/// assert_eq!(relative.resolve(&base)?.as_str(), "did:example:123#key-1");
32///
33/// let absolute = DidUrlReference::parse("did:example:456#key-1")?;
34/// assert!(!absolute.is_relative());
35/// assert_eq!(absolute.resolve(&base)?.as_str(), "did:example:456#key-1");
36/// # Ok::<(), identity_kit::did::DidError>(())
37/// ```
38#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub enum DidUrlReference {
40    /// A DID URL.
41    Absolute(DidUrl),
42    /// A relative DID URL: `/path`, `?query` or `#fragment`, possibly combined, as written.
43    Relative(String),
44}
45
46impl DidUrlReference {
47    /// Parses a DID URL or a relative DID URL, rejecting input longer than [`DEFAULT_MAX_LEN`]
48    /// bytes.
49    ///
50    /// # Errors
51    ///
52    /// A [`DidError`] when the input is neither a valid DID URL nor a relative reference starting
53    /// with `/`, `?` or `#` whose components are valid per RFC 3986. Positions in the error are
54    /// byte offsets into the input.
55    pub fn parse(s: &str) -> Result<Self, DidError> {
56        if s.starts_with(['/', '?', '#']) {
57            check_len(s, DEFAULT_MAX_LEN)?;
58            scan_url_suffix(s, 0)?;
59            Ok(Self::Relative(s.to_owned()))
60        } else {
61            DidUrl::parse(s).map(Self::Absolute)
62        }
63    }
64
65    /// Whether this is a relative reference.
66    pub fn is_relative(&self) -> bool {
67        matches!(self, Self::Relative(_))
68    }
69
70    /// The reference as written.
71    pub fn as_str(&self) -> &str {
72        match self {
73            Self::Absolute(url) => url.as_str(),
74            Self::Relative(s) => s,
75        }
76    }
77
78    /// The absolute DID URL, resolving a relative reference against `base` as RFC 3986 §5
79    /// prescribes for a DID document (DID Core §3.2.2).
80    ///
81    /// # Errors
82    ///
83    /// [`DidError::TooLong`] when `base` and a relative reference together exceed
84    /// [`DEFAULT_MAX_LEN`] bytes, the limit [`Did::join`] keeps to.
85    pub fn resolve(&self, base: &Did) -> Result<DidUrl, DidError> {
86        match self {
87            Self::Absolute(url) => Ok(url.clone()),
88            Self::Relative(s) => base.join(s),
89        }
90    }
91
92    /// Whether this reference denotes `url` in a document whose `id` is `base`.
93    pub fn refers_to(&self, url: &DidUrl, base: &Did) -> bool {
94        match self {
95            Self::Absolute(this) => this == url,
96            Self::Relative(_) => self.resolve(base).is_ok_and(|resolved| &resolved == url),
97        }
98    }
99}
100
101impl fmt::Display for DidUrlReference {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        f.write_str(self.as_str())
104    }
105}
106
107impl AsRef<str> for DidUrlReference {
108    fn as_ref(&self) -> &str {
109        self.as_str()
110    }
111}
112
113impl FromStr for DidUrlReference {
114    type Err = DidError;
115
116    fn from_str(s: &str) -> Result<Self, Self::Err> {
117        Self::parse(s)
118    }
119}
120
121impl TryFrom<&str> for DidUrlReference {
122    type Error = DidError;
123
124    fn try_from(s: &str) -> Result<Self, Self::Error> {
125        Self::parse(s)
126    }
127}
128
129impl TryFrom<String> for DidUrlReference {
130    type Error = DidError;
131
132    fn try_from(s: String) -> Result<Self, Self::Error> {
133        Self::parse(&s)
134    }
135}
136
137impl From<DidUrl> for DidUrlReference {
138    fn from(url: DidUrl) -> Self {
139        Self::Absolute(url)
140    }
141}
142
143impl From<&DidUrl> for DidUrlReference {
144    fn from(url: &DidUrl) -> Self {
145        Self::Absolute(url.clone())
146    }
147}
148
149impl Serialize for DidUrlReference {
150    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
151        serializer.serialize_str(self.as_str())
152    }
153}
154
155impl<'de> Deserialize<'de> for DidUrlReference {
156    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
157        let s = String::deserialize(deserializer)?;
158        Self::parse(&s).map_err(D::Error::custom)
159    }
160}