did_toolkit/
did.rs

1use crate::{
2    string::{method_id_encoded, url_encoded, validate_method_name},
3    url::{URLParameters, URL},
4};
5use anyhow::anyhow;
6use serde::{de::Visitor, Deserialize, Serialize};
7use std::fmt::Display;
8
9/// A DID is a decentralized identity according to <https://www.w3.org/TR/did-core/#did-syntax>. A
10/// DID internally is represented as a byte array that is percent-encoded on demand according to
11/// the rules defined in that document, as well as validated in some instances with regards to
12/// encoding requirements. DIDs are not required to be UTF-8 compliant in the ID portion, and all
13/// bytes that fall outside of a normal alphanumeric ASCII range are percent-encoded, with a few
14/// exceptions. The internal types are [`Vec<u8>`] for malleability but this may change to \[u8] in the
15/// future.
16///
17/// DIDs must have both a non-empty name and ID portion according to this interpretation of the
18/// spec. They must start with `did:` and will be generated as such both in string conversion and
19/// serialization steps. De-serialization also runs through the same checks and conversions.
20///
21/// ```
22/// use did_toolkit::prelude::*;
23///
24/// let did = DID::parse("did:mymethod:alice").unwrap();
25/// assert_eq!(String::from_utf8(did.name).unwrap(), "mymethod");
26/// assert_eq!(String::from_utf8(did.id).unwrap(), "alice");
27///
28/// let did = DID {
29///     name: "mymethod".as_bytes().to_vec(),
30///     id: "alice".as_bytes().to_vec(),
31/// };
32/// assert_eq!(did.to_string(), "did:mymethod:alice");
33/// ```
34#[derive(Clone, Hash, Default, Debug, PartialOrd, Ord, Eq, PartialEq)]
35pub struct DID {
36    pub name: Vec<u8>,
37    pub id: Vec<u8>,
38}
39
40impl Serialize for DID {
41    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
42    where
43        S: serde::Serializer,
44    {
45        serializer.serialize_str(&self.to_string())
46    }
47}
48
49struct DIDVisitor;
50impl Visitor<'_> for DIDVisitor {
51    type Value = DID;
52
53    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
54        formatter.write_str("Expecting a decentralized identity")
55    }
56
57    fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
58    where
59        E: serde::de::Error,
60    {
61        match DID::parse(&v) {
62            Ok(did) => Ok(did),
63            Err(e) => Err(E::custom(e)),
64        }
65    }
66
67    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
68    where
69        E: serde::de::Error,
70    {
71        match DID::parse(v) {
72            Ok(did) => Ok(did),
73            Err(e) => Err(E::custom(e)),
74        }
75    }
76}
77
78impl<'de> Deserialize<'de> for DID {
79    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80    where
81        D: serde::Deserializer<'de>,
82    {
83        deserializer.deserialize_any(DIDVisitor)
84    }
85}
86
87impl Display for DID {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        let mut ret = String::from("did:");
90
91        ret += &url_encoded(&self.name);
92        ret += &(":".to_string() + &method_id_encoded(&self.id));
93        f.write_str(&ret)
94    }
95}
96
97impl DID {
98    /// Parse a DID from a string. See top-level type documentation for information on formats.
99    pub fn parse(s: &str) -> Result<Self, anyhow::Error> {
100        match s.strip_prefix("did:") {
101            Some(s) => match s.split_once(':') {
102                Some((method_name, method_id)) => {
103                    if method_id.is_empty() {
104                        return Err(anyhow!("Method ID cannot be empty"));
105                    }
106
107                    if method_name.is_empty() {
108                        return Err(anyhow!("Method name cannot be empty"));
109                    }
110
111                    validate_method_name(method_name.as_bytes())?;
112                    Ok(DID {
113                        name: method_name.into(),
114                        id: method_id.into(),
115                    })
116                }
117                None => Err(anyhow!("DID is missing method_id")),
118            },
119            None => Err(anyhow!("DID is missing method_name, method_id")),
120        }
121    }
122
123    /// When provided with URL parameters, generates a DID URL. These are different from hypertext
124    /// URLs and should be handled differently.
125    ///
126    /// ```
127    /// use did_toolkit::prelude::*;
128    ///
129    /// let did = DID::parse("did:mymethod:alice").unwrap();
130    /// let url = did.join(URLParameters{
131    ///     fragment: Some("key-1".as_bytes().to_vec()),
132    ///     ..Default::default()
133    /// });
134    /// assert_eq!(url.to_string(), "did:mymethod:alice#key-1");
135    /// ```
136    pub fn join(&self, parameters: URLParameters) -> URL {
137        URL {
138            did: self.clone(),
139            parameters: Some(parameters),
140        }
141    }
142}
143
144mod tests {
145    #[test]
146    fn test_to_string() {
147        use super::DID;
148
149        let did = DID {
150            name: "abcdef".into(),
151            id: "123456".into(),
152            ..Default::default()
153        };
154
155        assert_eq!(did.to_string(), "did:abcdef:123456");
156
157        let did = DID {
158            name: "abcdef".into(),
159            id: "123456:u:alice".into(),
160            ..Default::default()
161        };
162
163        assert_eq!(did.to_string(), "did:abcdef:123456:u:alice");
164    }
165
166    #[test]
167    fn test_parse() {
168        use super::DID;
169
170        assert!(DID::parse("").is_err());
171        assert!(DID::parse("did::").is_err());
172        assert!(DID::parse("did:a:").is_err());
173        assert!(DID::parse("frobnik").is_err());
174        assert!(DID::parse("did").is_err());
175        assert!(DID::parse("frobnik:").is_err());
176        assert!(DID::parse("did:").is_err());
177        assert!(DID::parse("did:abcdef").is_err());
178
179        let did = DID::parse("did:abcdef:123456").unwrap();
180        assert_eq!(
181            did,
182            DID {
183                name: "abcdef".into(),
184                id: "123456".into(),
185                ..Default::default()
186            }
187        );
188
189        let did = DID::parse("did:abcdef:123456:u:alice").unwrap();
190        assert_eq!(
191            did,
192            DID {
193                name: "abcdef".into(),
194                id: "123456:u:alice".into(),
195                ..Default::default()
196            }
197        );
198    }
199
200    #[test]
201    fn test_serde() {
202        use super::DID;
203
204        let did: [DID; 1] = serde_json::from_str(r#"["did:123456:123"]"#).unwrap();
205        assert_eq!(
206            did[0],
207            DID {
208                name: "123456".into(),
209                id: "123".into(),
210                ..Default::default()
211            }
212        );
213
214        assert_eq!(
215            serde_json::to_string(&did).unwrap(),
216            r#"["did:123456:123"]"#
217        );
218    }
219}