1use 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 fn scheme(&self) -> &'static str {
39 self.as_ref().0.scheme()
40 }
41
42 fn authority(&self) -> &str {
48 self.as_ref().0.authority()
49 }
50
51 fn method(&self) -> &str {
57 self.as_ref().0.method()
58 }
59
60 fn method_id(&self) -> &str {
66 self.as_ref().0.method_id()
67 }
68
69 fn as_str(&self) -> &str {
73 self.as_ref().0.as_str()
74 }
75
76 fn into_string(self) -> String {
78 self.into()
79 }
80
81 fn join(self, value: impl AsRef<str>) -> Result<DIDUrl, Error> {
86 let url = DIDUrl::from(self);
87 url.join(value)
88 }
89
90 fn to_url(&self) -> DIDUrl {
92 DIDUrl::new(self.clone().into(), None)
93 }
94
95 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")]
104pub struct CoreDID(BaseDIDUrl);
106
107impl CoreDID {
108 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 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 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 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 pub fn valid_method_id(value: &str) -> Result<(), Error> {
142 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 pub fn check_validity(did: &BaseDIDUrl) -> Result<(), Error> {
164 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 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#[inline(always)]
273pub(crate) const fn is_char_method_name(ch: char) -> bool {
274 matches!(ch, '0'..='9' | 'a'..='z')
275}
276
277#[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}