1use core::fmt;
2use std::{borrow::Borrow, ops::Deref, str::FromStr};
3
4use crate::DIDBuf;
5
6use super::{Unexpected, DID};
7
8mod primary;
9mod reference;
10mod relative;
11
12use iref::{Iri, IriBuf, Uri, UriBuf};
13pub use primary::*;
14pub use reference::*;
15pub use relative::*;
16use serde::{Deserialize, Serialize};
17
18#[derive(Debug, thiserror::Error)]
20#[error("invalid DID URL `{0}`: {1}")]
21pub struct InvalidDIDURL<T>(pub T, pub Unexpected);
22
23impl<T> InvalidDIDURL<T> {
24 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> InvalidDIDURL<U> {
25 InvalidDIDURL(f(self.0), self.1)
26 }
27}
28
29#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
31#[repr(transparent)]
32pub struct DIDURL([u8]);
33
34impl DIDURL {
35 pub fn new<T: ?Sized + AsRef<[u8]>>(did_url: &T) -> Result<&Self, InvalidDIDURL<&T>> {
40 let data = did_url.as_ref();
41 match Self::validate(data) {
42 Ok(()) => Ok(unsafe {
43 std::mem::transmute::<&[u8], &Self>(data)
46 }),
47 Err(e) => Err(InvalidDIDURL(did_url, e)),
48 }
49 }
50
51 pub unsafe fn new_unchecked(data: &[u8]) -> &Self {
58 std::mem::transmute::<&[u8], &Self>(data)
61 }
62
63 pub fn as_iri(&self) -> &Iri {
64 unsafe {
65 Iri::new_unchecked(self.as_str())
67 }
68 }
69
70 pub fn as_uri(&self) -> &Uri {
71 unsafe {
72 Uri::new_unchecked(&self.0)
74 }
75 }
76
77 pub fn as_str(&self) -> &str {
79 unsafe {
80 std::str::from_utf8_unchecked(&self.0)
82 }
83 }
84
85 pub fn as_bytes(&self) -> &[u8] {
87 &self.0
88 }
89
90 fn path_offset(&self) -> usize {
91 self.0
92 .iter()
93 .position(|&b| matches!(b, b'/' | b'?' | b'#'))
94 .unwrap_or(self.0.len())
95 }
96
97 fn query_delimiter_offset(&self) -> usize {
98 self.0
99 .iter()
100 .position(|&b| matches!(b, b'?' | b'#'))
101 .unwrap_or(self.0.len())
102 }
103
104 fn query_delimiter_offset_from(&self, offset: usize) -> usize {
105 self.0[offset..]
106 .iter()
107 .position(|&b| matches!(b, b'?' | b'#'))
108 .map(|o| o + offset)
109 .unwrap_or(self.0.len())
110 }
111
112 fn fragment_delimiter_offset(&self) -> usize {
113 self.0
114 .iter()
115 .position(|&b| matches!(b, b'#'))
116 .unwrap_or(self.0.len())
117 }
118
119 fn fragment_delimiter_offset_from(&self, offset: usize) -> usize {
120 self.0[offset..]
121 .iter()
122 .position(|&b| matches!(b, b'#'))
123 .map(|o| o + offset)
124 .unwrap_or(self.0.len())
125 }
126
127 pub fn did(&self) -> &DID {
128 unsafe { DID::new_unchecked(&self.0[..self.path_offset()]) }
129 }
130
131 pub fn path(&self) -> &Path {
132 let start = self.path_offset();
133 let end = self.query_delimiter_offset_from(start);
134 unsafe { Path::new_unchecked(&self.0[start..end]) }
135 }
136
137 pub fn query(&self) -> Option<&Query> {
138 let start = self.query_delimiter_offset();
139 let end = self.fragment_delimiter_offset_from(start);
140 if start == end {
141 None
142 } else {
143 Some(unsafe { Query::new_unchecked(&self.0[(start + 1)..end]) })
144 }
145 }
146
147 pub fn fragment(&self) -> Option<&Fragment> {
148 let start = self.fragment_delimiter_offset();
149 let end = self.0.len();
150 if start == end {
151 None
152 } else {
153 Some(unsafe { Fragment::new_unchecked(&self.0[(start + 1)..end]) })
154 }
155 }
156
157 pub fn to_relative(&self, base_did: &DID) -> Option<&RelativeDIDURL> {
159 if self.did() != base_did {
160 None
161 } else {
162 let offset = self.path_offset();
163 Some(unsafe { RelativeDIDURL::new_unchecked(&self.0[offset..]) })
164 }
165 }
166
167 pub fn without_fragment(&self) -> (&PrimaryDIDURL, Option<&Fragment>) {
171 let fragment_start = self.fragment_delimiter_offset();
172 let fragment_end = self.0.len();
173
174 let fragment = if fragment_start == fragment_end {
175 None
176 } else {
177 Some(unsafe { Fragment::new_unchecked(&self.0[(fragment_start + 1)..fragment_end]) })
178 };
179
180 unsafe {
181 (
182 PrimaryDIDURL::new_unchecked(&self.0[..fragment_start]),
183 fragment,
184 )
185 }
186 }
187}
188
189impl PartialEq<DIDURLBuf> for DIDURL {
190 fn eq(&self, other: &DIDURLBuf) -> bool {
191 self == other.as_did_url()
192 }
193}
194
195impl PartialEq<DID> for DIDURL {
196 fn eq(&self, other: &DID) -> bool {
197 self.path().is_empty()
198 && self.query().is_none()
199 && self.fragment().is_none()
200 && self.did() == other
201 }
202}
203
204impl PartialEq<DIDBuf> for DIDURL {
205 fn eq(&self, other: &DIDBuf) -> bool {
206 self == other.as_did()
207 }
208}
209
210impl ToOwned for DIDURL {
211 type Owned = DIDURLBuf;
212
213 fn to_owned(&self) -> Self::Owned {
214 unsafe { DIDURLBuf::new_unchecked(self.0.to_vec()) }
215 }
216}
217
218impl Deref for DIDURL {
219 type Target = str;
220
221 fn deref(&self) -> &Self::Target {
222 self.as_str()
223 }
224}
225
226impl Borrow<Uri> for DIDURL {
227 fn borrow(&self) -> &Uri {
228 self.as_uri()
229 }
230}
231
232impl Borrow<Iri> for DIDURL {
233 fn borrow(&self) -> &Iri {
234 self.as_iri()
235 }
236}
237
238#[repr(transparent)]
240pub struct Path([u8]);
241
242impl Path {
243 pub unsafe fn new_unchecked(data: &[u8]) -> &Self {
249 std::mem::transmute(data)
250 }
251
252 pub fn as_str(&self) -> &str {
254 unsafe {
255 std::str::from_utf8_unchecked(&self.0)
257 }
258 }
259
260 pub fn as_bytes(&self) -> &[u8] {
262 &self.0
263 }
264}
265
266impl Deref for Path {
267 type Target = str;
268
269 fn deref(&self) -> &Self::Target {
270 self.as_str()
271 }
272}
273
274#[repr(transparent)]
276pub struct Query([u8]);
277
278impl Query {
279 pub unsafe fn new_unchecked(data: &[u8]) -> &Self {
285 std::mem::transmute(data)
286 }
287
288 pub fn as_str(&self) -> &str {
290 unsafe {
291 std::str::from_utf8_unchecked(&self.0)
293 }
294 }
295
296 pub fn as_bytes(&self) -> &[u8] {
298 &self.0
299 }
300}
301
302impl Deref for Query {
303 type Target = str;
304
305 fn deref(&self) -> &Self::Target {
306 self.as_str()
307 }
308}
309
310#[repr(transparent)]
311pub struct Fragment([u8]);
312
313impl Fragment {
314 pub unsafe fn new_unchecked(data: &[u8]) -> &Self {
320 std::mem::transmute(data)
321 }
322
323 pub fn as_str(&self) -> &str {
325 unsafe {
326 std::str::from_utf8_unchecked(&self.0)
328 }
329 }
330
331 pub fn as_bytes(&self) -> &[u8] {
333 &self.0
334 }
335}
336
337impl Deref for Fragment {
338 type Target = str;
339
340 fn deref(&self) -> &Self::Target {
341 self.as_str()
342 }
343}
344
345impl ToOwned for Fragment {
346 type Owned = FragmentBuf;
347
348 fn to_owned(&self) -> Self::Owned {
349 unsafe { FragmentBuf::new_unchecked(self.0.to_vec()) }
350 }
351}
352
353pub struct FragmentBuf(Vec<u8>);
354
355impl FragmentBuf {
356 pub unsafe fn new_unchecked(data: Vec<u8>) -> Self {
362 Self(data)
363 }
364
365 pub fn as_fragment(&self) -> &Fragment {
366 unsafe { Fragment::new_unchecked(&self.0) }
367 }
368}
369
370impl Deref for FragmentBuf {
371 type Target = Fragment;
372
373 fn deref(&self) -> &Self::Target {
374 self.as_fragment()
375 }
376}
377
378impl Borrow<Fragment> for FragmentBuf {
379 fn borrow(&self) -> &Fragment {
380 self.as_fragment()
381 }
382}
383
384#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
385pub struct DIDURLBuf(Vec<u8>);
386
387impl DIDURLBuf {
388 pub fn new(data: Vec<u8>) -> Result<Self, InvalidDIDURL<Vec<u8>>> {
389 match DIDURL::validate(&data) {
390 Ok(()) => Ok(Self(data)),
391 Err(e) => Err(InvalidDIDURL(data, e)),
392 }
393 }
394
395 pub fn from_string(data: String) -> Result<Self, InvalidDIDURL<String>> {
396 Self::new(data.into_bytes()).map_err(|InvalidDIDURL(bytes, e)| {
397 InvalidDIDURL(unsafe { String::from_utf8_unchecked(bytes) }, e)
398 })
399 }
400
401 pub unsafe fn new_unchecked(data: Vec<u8>) -> Self {
407 Self(data)
408 }
409
410 pub fn as_did_url(&self) -> &DIDURL {
411 unsafe { DIDURL::new_unchecked(&self.0) }
412 }
413
414 pub fn into_iri(self) -> IriBuf {
415 unsafe { IriBuf::new_unchecked(String::from_utf8_unchecked(self.0)) }
416 }
417
418 pub fn into_uri(self) -> UriBuf {
419 unsafe { UriBuf::new_unchecked(self.0) }
420 }
421
422 pub fn into_string(self) -> String {
423 unsafe { String::from_utf8_unchecked(self.0) }
424 }
425}
426
427impl FromStr for DIDURLBuf {
428 type Err = InvalidDIDURL<String>;
429
430 fn from_str(s: &str) -> Result<Self, Self::Err> {
431 s.to_owned().try_into()
432 }
433}
434
435impl TryFrom<String> for DIDURLBuf {
436 type Error = InvalidDIDURL<String>;
437
438 fn try_from(value: String) -> Result<Self, Self::Error> {
439 DIDURLBuf::new(value.into_bytes()).map_err(|e| {
440 e.map(|bytes| unsafe {
441 String::from_utf8_unchecked(bytes)
444 })
445 })
446 }
447}
448
449impl From<DIDURLBuf> for UriBuf {
450 fn from(value: DIDURLBuf) -> Self {
451 unsafe { UriBuf::new_unchecked(value.0) }
452 }
453}
454
455impl From<DIDURLBuf> for IriBuf {
456 fn from(value: DIDURLBuf) -> Self {
457 unsafe { IriBuf::new_unchecked(String::from_utf8_unchecked(value.0)) }
458 }
459}
460
461impl From<DIDURLBuf> for String {
462 fn from(value: DIDURLBuf) -> Self {
463 value.into_string()
464 }
465}
466
467impl Deref for DIDURLBuf {
468 type Target = DIDURL;
469
470 fn deref(&self) -> &Self::Target {
471 self.as_did_url()
472 }
473}
474
475impl Borrow<DIDURL> for DIDURLBuf {
476 fn borrow(&self) -> &DIDURL {
477 self.as_did_url()
478 }
479}
480
481impl Borrow<Uri> for DIDURLBuf {
482 fn borrow(&self) -> &Uri {
483 self.as_uri()
484 }
485}
486
487impl Borrow<Iri> for DIDURLBuf {
488 fn borrow(&self) -> &Iri {
489 self.as_iri()
490 }
491}
492
493impl AsRef<[u8]> for DIDURLBuf {
494 fn as_ref(&self) -> &[u8] {
495 self.as_bytes()
496 }
497}
498
499impl fmt::Display for DIDURLBuf {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 self.as_str().fmt(f)
502 }
503}
504
505impl fmt::Debug for DIDURLBuf {
506 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
507 self.as_str().fmt(f)
508 }
509}
510
511impl PartialEq<str> for DIDURLBuf {
512 fn eq(&self, other: &str) -> bool {
513 self.as_str() == other
514 }
515}
516
517impl<'a> PartialEq<&'a str> for DIDURLBuf {
518 fn eq(&self, other: &&'a str) -> bool {
519 self.as_str() == *other
520 }
521}
522
523impl PartialEq<String> for DIDURLBuf {
524 fn eq(&self, other: &String) -> bool {
525 self.as_str() == *other
526 }
527}
528
529impl PartialEq<DIDURL> for DIDURLBuf {
530 fn eq(&self, other: &DIDURL) -> bool {
531 self.as_did_url() == other
532 }
533}
534
535impl<'a> PartialEq<&'a DIDURL> for DIDURLBuf {
536 fn eq(&self, other: &&'a DIDURL) -> bool {
537 self.as_did_url() == *other
538 }
539}
540
541impl PartialEq<DID> for DIDURLBuf {
542 fn eq(&self, other: &DID) -> bool {
543 self.as_did_url() == other
544 }
545}
546
547impl<'a> PartialEq<&'a DID> for DIDURLBuf {
548 fn eq(&self, other: &&'a DID) -> bool {
549 self.as_did_url() == *other
550 }
551}
552
553impl PartialEq<DIDBuf> for DIDURLBuf {
554 fn eq(&self, other: &DIDBuf) -> bool {
555 self.as_did_url() == other
556 }
557}
558
559impl Serialize for DIDURLBuf {
560 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
561 where
562 S: serde::Serializer,
563 {
564 self.as_str().serialize(serializer)
565 }
566}
567
568impl<'de> Deserialize<'de> for DIDURLBuf {
569 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
570 where
571 D: serde::Deserializer<'de>,
572 {
573 struct Visitor;
574
575 impl serde::de::Visitor<'_> for Visitor {
576 type Value = DIDURLBuf;
577
578 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
579 write!(f, "a relative DID URL")
580 }
581
582 fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
583 where
584 E: serde::de::Error,
585 {
586 v.try_into().map_err(|e| E::custom(e))
587 }
588
589 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
590 where
591 E: serde::de::Error,
592 {
593 self.visit_string(v.to_string())
594 }
595 }
596
597 deserializer.deserialize_string(Visitor)
598 }
599}
600
601impl DIDURL {
602 fn validate(data: &[u8]) -> Result<(), Unexpected> {
604 let mut bytes = data.iter().copied();
605 match Self::validate_from(0, &mut bytes)? {
606 (_, None) => Ok(()),
607 (i, Some(c)) => Err(Unexpected(i, Some(c))),
608 }
609 }
610
611 fn validate_from(
613 i: usize,
614 bytes: &mut impl Iterator<Item = u8>,
615 ) -> Result<(usize, Option<u8>), Unexpected> {
616 match DID::validate_from(i, bytes)? {
617 (i, None) => Ok((i, None)),
618 (mut i, Some(c)) => {
619 enum State {
620 PathSegment,
621 Query,
622 Fragment,
623 Pct1(Part),
624 Pct2(Part),
625 }
626
627 enum Part {
628 PathSegment,
629 Query,
630 Fragment,
631 }
632
633 impl Part {
634 pub fn state(&self) -> State {
635 match self {
636 Self::PathSegment => State::PathSegment,
637 Self::Query => State::Query,
638 Self::Fragment => State::Fragment,
639 }
640 }
641 }
642
643 let mut state = match c {
644 b'/' => State::PathSegment,
645 b'?' => State::Query,
646 b'#' => State::Fragment,
647 c => return Err(Unexpected(i, Some(c))),
648 };
649
650 fn is_unreserved(b: u8) -> bool {
651 b.is_ascii_alphanumeric() || matches!(b, b'-' | b'.' | b'_' | b'~')
652 }
653
654 fn is_sub_delims(b: u8) -> bool {
655 matches!(
656 b,
657 b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b';' | b'='
658 )
659 }
660
661 fn is_pchar(b: u8) -> bool {
662 is_unreserved(b) || is_sub_delims(b) || matches!(b, b':' | b'@')
663 }
664
665 loop {
666 match state {
667 State::PathSegment => match bytes.next() {
668 Some(b'/') => (), Some(b'?') => state = State::Query,
670 Some(b'#') => state = State::Fragment,
671 Some(b'%') => state = State::Pct1(Part::PathSegment),
672 Some(c) if is_pchar(c) => (),
673 c => break Ok((i, c)),
674 },
675 State::Query => match bytes.next() {
676 Some(b'#') => state = State::Fragment,
677 Some(b'%') => state = State::Pct1(Part::Query),
678 Some(c) if is_pchar(c) || matches!(c, b'/' | b'?') => (),
679 c => break Ok((i, c)),
680 },
681 State::Fragment => match bytes.next() {
682 Some(b'%') => state = State::Pct1(Part::Fragment),
683 Some(c) if is_pchar(c) || matches!(c, b'/' | b'?' | b'#') => (),
684 c => break Ok((i, c)),
685 },
686 State::Pct1(q) => match bytes.next() {
687 Some(c) if c.is_ascii_hexdigit() => state = State::Pct2(q),
688 c => break Err(Unexpected(i, c)),
689 },
690 State::Pct2(q) => match bytes.next() {
691 Some(c) if c.is_ascii_hexdigit() => state = q.state(),
692 c => break Err(Unexpected(i, c)),
693 },
694 }
695
696 i += 1
697 }
698 }
699 }
700 }
701}
702
703#[cfg(test)]
704mod tests {
705 use super::*;
706
707 #[test]
708 fn parse_did_url_accept() {
709 let vectors: [&[u8]; 4] = [
710 b"did:method:foo",
711 b"did:a:b",
712 b"did:a:b#fragment",
713 b"did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9#key"
714 ];
715
716 for input in vectors {
717 DIDURL::new(input).unwrap();
718 }
719 }
720
721 #[test]
722 fn parse_did_url_reject() {
723 let vectors: [&[u8]; 3] = [b"http:a:b", b"did::b", b"did:a:"];
724
725 for input in vectors {
726 assert!(DIDURL::new(input).is_err())
727 }
728 }
729}