1use core::fmt;
2use std::{borrow::Borrow, ops::Deref, str::FromStr};
3
4mod url;
5
6use iref::{Iri, IriBuf, Uri, UriBuf};
7use serde::{Deserialize, Serialize};
8pub use url::*;
9
10#[derive(Debug, thiserror::Error)]
12#[error("invalid DID `{0}`: {1}")]
13pub struct InvalidDID<T>(pub T, pub Unexpected);
14
15impl<T> InvalidDID<T> {
16 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> InvalidDID<U> {
17 InvalidDID(f(self.0), self.1)
18 }
19}
20
21#[macro_export]
22macro_rules! did {
23 ($did:literal) => {
24 $crate::DID::new($did).unwrap()
25 };
26}
27
28#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
33#[repr(transparent)]
34pub struct DID([u8]);
35
36impl DID {
37 pub fn new<B: ?Sized + AsRef<[u8]>>(data: &B) -> Result<&Self, InvalidDID<&B>> {
42 let bytes = data.as_ref();
43 match Self::validate(bytes) {
44 Ok(()) => Ok(unsafe {
45 std::mem::transmute::<&[u8], &Self>(bytes)
48 }),
49 Err(e) => Err(InvalidDID(data, e)),
50 }
51 }
52
53 pub unsafe fn new_unchecked(data: &[u8]) -> &Self {
60 std::mem::transmute(data)
63 }
64
65 pub fn as_iri(&self) -> &Iri {
66 unsafe {
67 Iri::new_unchecked(self.as_str())
69 }
70 }
71
72 pub fn as_uri(&self) -> &Uri {
73 unsafe {
74 Uri::new_unchecked(&self.0)
76 }
77 }
78
79 pub fn as_str(&self) -> &str {
81 unsafe {
82 std::str::from_utf8_unchecked(&self.0)
84 }
85 }
86
87 pub fn as_bytes(&self) -> &[u8] {
89 &self.0
90 }
91
92 fn method_name_separator_offset(&self) -> usize {
94 self.0[5..].iter().position(|b| *b == b':').unwrap() + 5 }
96
97 pub fn method_name_bytes(&self) -> &[u8] {
99 &self.0[4..self.method_name_separator_offset()]
100 }
101
102 pub fn method_name(&self) -> &str {
104 unsafe {
105 std::str::from_utf8_unchecked(self.method_name_bytes())
107 }
108 }
109
110 pub fn method_specific_id_bytes(&self) -> &[u8] {
112 &self.0[self.method_name_separator_offset() + 1..]
113 }
114
115 pub fn method_specific_id(&self) -> &str {
117 unsafe {
118 std::str::from_utf8_unchecked(self.method_specific_id_bytes())
120 }
121 }
122}
123
124impl Deref for DID {
125 type Target = str;
126
127 fn deref(&self) -> &Self::Target {
128 self.as_str()
129 }
130}
131
132impl Borrow<Uri> for DID {
133 fn borrow(&self) -> &Uri {
134 self.as_uri()
135 }
136}
137
138impl Borrow<Iri> for DID {
139 fn borrow(&self) -> &Iri {
140 self.as_iri()
141 }
142}
143
144impl PartialEq<DIDBuf> for DID {
145 fn eq(&self, other: &DIDBuf) -> bool {
146 self == other.as_did()
147 }
148}
149
150impl PartialEq<DIDURL> for DID {
151 fn eq(&self, other: &DIDURL) -> bool {
152 other == self
153 }
154}
155
156impl PartialEq<DIDURLBuf> for DID {
157 fn eq(&self, other: &DIDURLBuf) -> bool {
158 other == self
159 }
160}
161
162impl ToOwned for DID {
163 type Owned = DIDBuf;
164
165 fn to_owned(&self) -> Self::Owned {
166 unsafe { DIDBuf::new_unchecked(self.as_bytes().to_vec()) }
167 }
168}
169
170impl fmt::Display for DID {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 self.as_str().fmt(f)
173 }
174}
175
176#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
178pub struct DIDBuf(Vec<u8>);
179
180impl DIDBuf {
181 pub fn new(data: Vec<u8>) -> Result<Self, InvalidDID<Vec<u8>>> {
182 match DID::validate(&data) {
183 Ok(()) => Ok(Self(data)),
184 Err(e) => Err(InvalidDID(data, e)),
185 }
186 }
187
188 pub fn from_string(data: String) -> Result<Self, InvalidDID<String>> {
189 Self::new(data.into_bytes()).map_err(|InvalidDID(bytes, e)| {
190 InvalidDID(unsafe { String::from_utf8_unchecked(bytes) }, e)
191 })
192 }
193
194 pub unsafe fn new_unchecked(data: Vec<u8>) -> Self {
200 Self(data)
201 }
202
203 pub fn as_did(&self) -> &DID {
204 unsafe {
205 DID::new_unchecked(&self.0)
207 }
208 }
209
210 pub fn as_did_url(&self) -> &DIDURL {
211 unsafe {
212 DIDURL::new_unchecked(&self.0)
214 }
215 }
216
217 pub fn into_iri(self) -> IriBuf {
218 unsafe { IriBuf::new_unchecked(String::from_utf8_unchecked(self.0)) }
219 }
220
221 pub fn into_uri(self) -> UriBuf {
222 unsafe { UriBuf::new_unchecked(self.0) }
223 }
224
225 pub fn into_string(self) -> String {
226 unsafe { String::from_utf8_unchecked(self.0) }
227 }
228}
229
230impl TryFrom<String> for DIDBuf {
231 type Error = InvalidDID<String>;
232
233 fn try_from(value: String) -> Result<Self, Self::Error> {
234 DIDBuf::new(value.into_bytes()).map_err(|e| {
235 e.map(|bytes| unsafe {
236 String::from_utf8_unchecked(bytes)
239 })
240 })
241 }
242}
243
244impl FromStr for DIDBuf {
245 type Err = InvalidDID<String>;
246
247 fn from_str(s: &str) -> Result<Self, Self::Err> {
248 s.to_owned().try_into()
249 }
250}
251
252impl From<DIDBuf> for UriBuf {
253 fn from(value: DIDBuf) -> Self {
254 value.into_uri()
255 }
256}
257
258impl From<DIDBuf> for IriBuf {
259 fn from(value: DIDBuf) -> Self {
260 value.into_iri()
261 }
262}
263
264impl Deref for DIDBuf {
265 type Target = DID;
266
267 fn deref(&self) -> &Self::Target {
268 self.as_did()
269 }
270}
271
272impl Borrow<DID> for DIDBuf {
273 fn borrow(&self) -> &DID {
274 self.as_did()
275 }
276}
277
278impl Borrow<DIDURL> for DIDBuf {
279 fn borrow(&self) -> &DIDURL {
280 self.as_did_url()
281 }
282}
283
284impl Borrow<Uri> for DIDBuf {
285 fn borrow(&self) -> &Uri {
286 self.as_uri()
287 }
288}
289
290impl Borrow<Iri> for DIDBuf {
291 fn borrow(&self) -> &Iri {
292 self.as_iri()
293 }
294}
295
296impl fmt::Display for DIDBuf {
297 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298 self.as_str().fmt(f)
299 }
300}
301
302impl fmt::Debug for DIDBuf {
303 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
304 self.as_str().fmt(f)
305 }
306}
307
308impl PartialEq<str> for DIDBuf {
309 fn eq(&self, other: &str) -> bool {
310 self.as_str() == other
311 }
312}
313
314impl<'a> PartialEq<&'a str> for DIDBuf {
315 fn eq(&self, other: &&'a str) -> bool {
316 self.as_str() == *other
317 }
318}
319
320impl PartialEq<DID> for DIDBuf {
321 fn eq(&self, other: &DID) -> bool {
322 self.as_did() == other
323 }
324}
325
326impl<'a> PartialEq<&'a DID> for DIDBuf {
327 fn eq(&self, other: &&'a DID) -> bool {
328 self.as_did() == *other
329 }
330}
331
332impl PartialEq<DIDURL> for DIDBuf {
333 fn eq(&self, other: &DIDURL) -> bool {
334 self.as_did() == other
335 }
336}
337
338impl<'a> PartialEq<&'a DIDURL> for DIDBuf {
339 fn eq(&self, other: &&'a DIDURL) -> bool {
340 self.as_did() == *other
341 }
342}
343
344impl PartialEq<DIDURLBuf> for DIDBuf {
345 fn eq(&self, other: &DIDURLBuf) -> bool {
346 self.as_did() == other
347 }
348}
349
350impl Serialize for DIDBuf {
351 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
352 where
353 S: serde::Serializer,
354 {
355 self.as_str().serialize(serializer)
356 }
357}
358
359impl<'de> Deserialize<'de> for DIDBuf {
360 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
361 where
362 D: serde::Deserializer<'de>,
363 {
364 struct Visitor;
365
366 impl serde::de::Visitor<'_> for Visitor {
367 type Value = DIDBuf;
368
369 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
370 write!(f, "a DID")
371 }
372
373 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
374 where
375 E: serde::de::Error,
376 {
377 v.try_into().map_err(|e| E::custom(e))
378 }
379
380 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
381 where
382 E: serde::de::Error,
383 {
384 self.visit_string(v.to_string())
385 }
386 }
387
388 deserializer.deserialize_string(Visitor)
389 }
390}
391
392#[derive(Debug, thiserror::Error)]
393pub struct Unexpected(pub usize, pub Option<u8>);
394
395impl fmt::Display for Unexpected {
396 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397 match self.1 {
398 Some(b) => write!(f, "unexpected byte {b} at offset {0:#04x}", self.0),
399 None => write!(f, "unexpected end at offset {0:#04x}", self.0),
400 }
401 }
402}
403
404impl DID {
405 fn validate(data: &[u8]) -> Result<(), Unexpected> {
407 let mut bytes = data.iter().copied();
408 match Self::validate_from(0, &mut bytes)? {
409 (_, None) => Ok(()),
410 (i, Some(c)) => Err(Unexpected(i, Some(c))),
411 }
412 }
413
414 fn validate_from(
416 mut i: usize,
417 bytes: &mut impl Iterator<Item = u8>,
418 ) -> Result<(usize, Option<u8>), Unexpected> {
419 enum State {
420 Scheme1, Scheme2, Scheme3, SchemeSeparator, MethodNameStart,
425 MethodName,
426 MethodSpecificIdStartOrSeparator,
427 MethodSpecificIdPct1,
428 MethodSpecificIdPct2,
429 MethodSpecificId,
430 }
431
432 let mut state = State::Scheme1;
433 fn is_method_char(b: u8) -> bool {
434 matches!(b, 0x61..=0x7a) || b.is_ascii_digit()
435 }
436
437 fn is_id_char(b: u8) -> bool {
438 b.is_ascii_alphanumeric() || matches!(b, b'.' | b'-' | b'_')
439 }
440
441 loop {
442 match state {
443 State::Scheme1 => match bytes.next() {
444 Some(b'd') => state = State::Scheme2,
445 c => break Err(Unexpected(i, c)),
446 },
447 State::Scheme2 => match bytes.next() {
448 Some(b'i') => state = State::Scheme3,
449 c => break Err(Unexpected(i, c)),
450 },
451 State::Scheme3 => match bytes.next() {
452 Some(b'd') => state = State::SchemeSeparator,
453 c => break Err(Unexpected(i, c)),
454 },
455 State::SchemeSeparator => match bytes.next() {
456 Some(b':') => state = State::MethodNameStart,
457 c => break Err(Unexpected(i, c)),
458 },
459 State::MethodNameStart => match bytes.next() {
460 Some(c) if is_method_char(c) => state = State::MethodName,
461 c => break Err(Unexpected(i, c)),
462 },
463 State::MethodName => match bytes.next() {
464 Some(b':') => state = State::MethodSpecificIdStartOrSeparator,
465 Some(c) if is_method_char(c) => (),
466 c => break Err(Unexpected(i, c)),
467 },
468 State::MethodSpecificIdStartOrSeparator => match bytes.next() {
469 Some(b':') => (),
470 Some(b'%') => state = State::MethodSpecificIdPct1,
471 Some(c) if is_id_char(c) => state = State::MethodSpecificId,
472 c => break Err(Unexpected(i, c)),
473 },
474 State::MethodSpecificIdPct1 => match bytes.next() {
475 Some(c) if c.is_ascii_hexdigit() => state = State::MethodSpecificIdPct2,
476 c => break Err(Unexpected(i, c)),
477 },
478 State::MethodSpecificIdPct2 => match bytes.next() {
479 Some(c) if c.is_ascii_hexdigit() => state = State::MethodSpecificId,
480 c => break Err(Unexpected(i, c)),
481 },
482 State::MethodSpecificId => match bytes.next() {
483 Some(b':') => state = State::MethodSpecificIdStartOrSeparator,
484 Some(b'%') => state = State::MethodSpecificIdPct1,
485 Some(c) if is_id_char(c) => (),
486 c => break Ok((i, c)),
487 },
488 }
489
490 i += 1
491 }
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498
499 #[test]
500 fn parse_did_accept() {
501 let vectors: [&[u8]; 4] = [
502 b"did:method:foo",
503 b"did:a:b",
504 b"did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9",
505 b"did:web:example.com%3A443:u:bob"
506 ];
507
508 for input in vectors {
509 DID::new(input).unwrap();
510 }
511 }
512
513 #[test]
514 fn parse_did_reject() {
515 let vectors: [&[u8]; 3] = [b"http:a:b", b"did::b", b"did:a:"];
516
517 for input in vectors {
518 assert!(DID::new(input).is_err())
519 }
520 }
521}