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