1use core::convert::TryFrom;
5use core::fmt::Debug;
6use core::fmt::Display;
7use core::fmt::Formatter;
8use core::str::FromStr;
9use std::cmp::Ordering;
10use std::hash::Hash;
11use std::hash::Hasher;
12
13use did_url_parser::DID as BaseDIDUrl;
14
15use identity_core::common::KeyComparable;
16use identity_core::common::Url;
17
18use crate::did::is_char_method_id;
19use crate::did::CoreDID;
20use crate::did::DID;
21use crate::Error;
22
23#[derive(Clone, serde::Deserialize, serde::Serialize)]
29#[serde(into = "String", try_from = "String")]
30pub struct DIDUrl {
31 did: CoreDID,
32 url: RelativeDIDUrl,
33}
34
35#[derive(Clone, Default)]
49pub struct RelativeDIDUrl {
50 path: Option<String>,
52 query: Option<String>,
54 fragment: Option<String>,
56}
57
58impl RelativeDIDUrl {
59 pub fn new() -> Self {
61 Self {
62 path: None,
63 query: None,
64 fragment: None,
65 }
66 }
67
68 pub fn is_empty(&self) -> bool {
70 self.path.as_deref().unwrap_or_default().is_empty()
71 && self.query.as_deref().unwrap_or_default().is_empty()
72 && self.fragment.as_deref().unwrap_or_default().is_empty()
73 }
74
75 pub fn path(&self) -> Option<&str> {
80 self.path.as_deref()
81 }
82
83 pub fn set_path(&mut self, value: Option<&str>) -> Result<(), Error> {
96 self.path = value
97 .filter(|s| !s.is_empty())
98 .map(|s| {
99 if s.starts_with('/') && is_valid_url_segment(s, is_char_path) {
100 Ok(s.to_owned())
101 } else {
102 Err(Error::InvalidPath)
103 }
104 })
105 .transpose()?;
106 Ok(())
107 }
108
109 pub fn query(&self) -> Option<&str> {
114 self.query.as_deref().and_then(|query| query.strip_prefix('?'))
115 }
116
117 pub fn set_query(&mut self, value: Option<&str>) -> Result<(), Error> {
136 self.query = value
137 .filter(|s| !s.is_empty())
138 .map(|mut s| {
139 s = s.strip_prefix('?').unwrap_or(s);
141 if s.is_empty() || !is_valid_url_segment(s, is_char_query) {
142 return Err(Error::InvalidQuery);
143 }
144 Ok(format!("?{s}"))
145 })
146 .transpose()?;
147 Ok(())
148 }
149
150 pub fn query_pairs(&self) -> form_urlencoded::Parse<'_> {
156 form_urlencoded::parse(self.query().unwrap_or_default().as_bytes())
157 }
158
159 pub fn fragment(&self) -> Option<&str> {
164 self.fragment.as_deref().and_then(|fragment| fragment.strip_prefix('#'))
165 }
166
167 pub fn set_fragment(&mut self, value: Option<&str>) -> Result<(), Error> {
186 self.fragment = value
187 .filter(|s| !s.is_empty())
188 .map(|mut s| {
189 s = s.strip_prefix('#').unwrap_or(s);
191 if s.is_empty() || !is_valid_url_segment(s, is_char_fragment) {
192 return Err(Error::InvalidFragment);
193 }
194 Ok(format!("#{s}"))
195 })
196 .transpose()?;
197 Ok(())
198 }
199}
200
201impl Display for RelativeDIDUrl {
202 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
203 f.write_fmt(format_args!(
204 "{}{}{}",
205 self.path.as_deref().unwrap_or_default(),
206 self.query.as_deref().unwrap_or_default(),
207 self.fragment.as_deref().unwrap_or_default()
208 ))
209 }
210}
211
212impl Debug for RelativeDIDUrl {
213 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
214 f.write_fmt(format_args!("{self}"))
215 }
216}
217
218impl PartialEq for RelativeDIDUrl {
219 fn eq(&self, other: &Self) -> bool {
220 self.path.as_deref().unwrap_or_default() == other.path.as_deref().unwrap_or_default()
221 && self.query.as_deref().unwrap_or_default() == other.query.as_deref().unwrap_or_default()
222 && self.fragment.as_deref().unwrap_or_default() == other.fragment.as_deref().unwrap_or_default()
223 }
224}
225
226impl Eq for RelativeDIDUrl {}
227
228impl PartialOrd for RelativeDIDUrl {
229 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
230 Some(self.cmp(other))
231 }
232}
233
234impl Ord for RelativeDIDUrl {
235 fn cmp(&self, other: &Self) -> Ordering {
236 let path_cmp = self
238 .path
239 .as_deref()
240 .unwrap_or_default()
241 .cmp(other.path.as_deref().unwrap_or_default());
242
243 if path_cmp == Ordering::Equal {
244 let query_cmp = self
245 .query
246 .as_deref()
247 .unwrap_or_default()
248 .cmp(other.query.as_deref().unwrap_or_default());
249
250 if query_cmp == Ordering::Equal {
251 return self
252 .fragment
253 .as_deref()
254 .unwrap_or_default()
255 .cmp(other.fragment.as_deref().unwrap_or_default());
256 }
257
258 return query_cmp;
259 }
260
261 path_cmp
262 }
263}
264
265impl Hash for RelativeDIDUrl {
266 fn hash<H: Hasher>(&self, state: &mut H) {
267 self.to_string().hash(state)
268 }
269}
270
271impl DIDUrl {
272 pub fn new(did: CoreDID, url: Option<RelativeDIDUrl>) -> Self {
274 Self {
275 did,
276 url: url.unwrap_or_default(),
277 }
278 }
279
280 pub fn parse(input: impl AsRef<str>) -> Result<Self, Error> {
282 let did_url: BaseDIDUrl = BaseDIDUrl::parse(input)?;
283 Self::from_base_did_url(did_url)
284 }
285
286 fn from_base_did_url(did_url: BaseDIDUrl) -> Result<Self, Error> {
287 let url: RelativeDIDUrl = {
289 let mut url: RelativeDIDUrl = RelativeDIDUrl::new();
290 url.set_path(Some(did_url.path()))?;
291 url.set_query(did_url.query())?;
292 url.set_fragment(did_url.fragment())?;
293 url
294 };
295
296 let did: CoreDID = {
298 let mut base_did: BaseDIDUrl = did_url;
299 base_did.set_path("");
300 base_did.set_query(None);
301 base_did.set_fragment(None);
302 CoreDID::try_from(base_did).map_err(|_| Error::Other("invalid DID"))?
303 };
304
305 Ok(Self { did, url })
306 }
307
308 pub fn did(&self) -> &CoreDID {
310 &self.did
311 }
312
313 pub fn url(&self) -> &RelativeDIDUrl {
315 &self.url
316 }
317
318 pub fn set_url(&mut self, url: RelativeDIDUrl) {
320 self.url = url
321 }
322
323 pub fn fragment(&self) -> Option<&str> {
327 self.url.fragment()
328 }
329
330 pub fn set_fragment(&mut self, value: Option<&str>) -> Result<(), Error> {
334 self.url.set_fragment(value)
335 }
336
337 pub fn path(&self) -> Option<&str> {
341 self.url.path()
342 }
343
344 pub fn set_path(&mut self, value: Option<&str>) -> Result<(), Error> {
348 self.url.set_path(value)
349 }
350
351 pub fn query(&self) -> Option<&str> {
355 self.url.query()
356 }
357
358 pub fn set_query(&mut self, value: Option<&str>) -> Result<(), Error> {
362 self.url.set_query(value)
363 }
364
365 pub fn query_pairs(&self) -> form_urlencoded::Parse<'_> {
369 self.url.query_pairs()
370 }
371
372 pub fn join(&self, segment: impl AsRef<str>) -> Result<Self, Error> {
382 let segment: &str = segment.as_ref();
383
384 if !segment.starts_with('/') && !segment.starts_with('?') && !segment.starts_with('#') {
386 return Err(Error::InvalidPath);
387 }
388
389 let base_did_url: BaseDIDUrl = BaseDIDUrl::parse(self.to_string())?.join(segment)?;
391 Self::from_base_did_url(base_did_url)
392 }
393
394 pub fn map<F>(self, f: F) -> DIDUrl
397 where
398 F: FnOnce(CoreDID) -> CoreDID,
399 {
400 DIDUrl {
401 did: f(self.did),
402 url: self.url,
403 }
404 }
405
406 pub fn try_map<F, E>(self, f: F) -> Result<DIDUrl, E>
408 where
409 F: FnOnce(CoreDID) -> Result<CoreDID, E>,
410 {
411 Ok(DIDUrl {
412 did: f(self.did)?,
413 url: self.url,
414 })
415 }
416}
417
418impl<D> From<D> for DIDUrl
419where
420 D: Into<CoreDID>,
421{
422 fn from(did: D) -> Self {
423 Self::new(did.into(), None)
424 }
425}
426
427impl FromStr for DIDUrl {
428 type Err = Error;
429
430 fn from_str(string: &str) -> Result<Self, Self::Err> {
431 Self::parse(string)
432 }
433}
434
435impl TryFrom<String> for DIDUrl {
436 type Error = Error;
437
438 fn try_from(other: String) -> Result<Self, Self::Error> {
439 Self::parse(other)
440 }
441}
442
443impl From<DIDUrl> for String {
444 fn from(did_url: DIDUrl) -> Self {
445 did_url.to_string()
446 }
447}
448
449impl From<DIDUrl> for Url {
450 fn from(did_url: DIDUrl) -> Self {
451 Url::parse(did_url.to_string()).expect("a DIDUrl should be a valid Url")
452 }
453}
454
455impl AsRef<CoreDID> for DIDUrl {
456 fn as_ref(&self) -> &CoreDID {
457 &self.did
458 }
459}
460
461impl AsRef<DIDUrl> for DIDUrl {
462 fn as_ref(&self) -> &DIDUrl {
463 self
464 }
465}
466
467impl PartialEq for DIDUrl {
468 fn eq(&self, other: &Self) -> bool {
469 self.did().eq(other.did()) && self.url() == other.url()
470 }
471}
472
473impl Eq for DIDUrl {}
474
475impl PartialOrd for DIDUrl {
476 #[inline]
477 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
478 Some(self.cmp(other))
479 }
480}
481
482impl Ord for DIDUrl {
483 #[inline]
484 fn cmp(&self, other: &Self) -> Ordering {
485 match self.did().cmp(other.did()) {
486 Ordering::Equal => self.url().cmp(other.url()),
487 ord => ord,
488 }
489 }
490}
491
492impl Hash for DIDUrl {
493 fn hash<H: Hasher>(&self, state: &mut H) {
494 self.to_string().hash(state)
495 }
496}
497
498impl Debug for DIDUrl {
499 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
500 f.write_fmt(format_args!("{self}"))
501 }
502}
503
504impl Display for DIDUrl {
505 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
506 f.write_fmt(format_args!("{}{}", self.did.as_str(), self.url))
507 }
508}
509
510impl KeyComparable for DIDUrl {
511 type Key = Self;
512
513 fn key(&self) -> &Self::Key {
514 self
515 }
516}
517
518#[inline(always)]
520#[rustfmt::skip]
521pub(crate) const fn is_char_path(ch: char) -> bool {
522 is_char_method_id(ch) || matches!(ch, '~' | '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | ';' | '=' | '@' | '/')
523}
524
525#[inline(always)]
527pub(crate) const fn is_char_query(ch: char) -> bool {
528 is_char_path(ch) || ch == '?'
529}
530
531#[inline(always)]
533pub(crate) const fn is_char_fragment(ch: char) -> bool {
534 is_char_path(ch) || ch == '?'
535}
536
537pub(crate) fn is_valid_percent_encoded_char(s: &str) -> bool {
538 let mut chars = s.chars();
539 let Some('%') = chars.next() else { return false };
540 s.len() >= 3 && chars.take(2).all(|c| c.is_ascii_hexdigit())
541}
542
543pub(crate) fn is_valid_url_segment<F>(segment: &str, char_predicate: F) -> bool
544where
545 F: Fn(char) -> bool,
546{
547 let mut chars = segment.char_indices();
548 while let Some((i, c)) = chars.next() {
549 if c == '%' {
550 if !is_valid_percent_encoded_char(&segment[i..]) {
551 return false;
552 }
553 chars.next();
555 chars.next();
556 } else if !char_predicate(c) {
557 return false;
558 }
559 }
560
561 true
562}
563
564#[cfg(test)]
565mod tests {
566 use super::*;
567
568 #[rustfmt::skip]
569 #[test]
570 fn test_did_url_parse_valid() {
571 let did_url = DIDUrl::parse("did:example:1234567890").unwrap();
572 assert_eq!(did_url.to_string(), "did:example:1234567890");
573 assert!(did_url.url().is_empty());
574 assert!(did_url.path().is_none());
575 assert!(did_url.query().is_none());
576 assert!(did_url.fragment().is_none());
577
578 assert_eq!(DIDUrl::parse("did:example:1234567890/path").unwrap().to_string(), "did:example:1234567890/path");
579 assert_eq!(DIDUrl::parse("did:example:1234567890?query").unwrap().to_string(), "did:example:1234567890?query");
580 assert_eq!(DIDUrl::parse("did:example:1234567890#fragment").unwrap().to_string(), "did:example:1234567890#fragment");
581
582 assert_eq!(DIDUrl::parse("did:example:1234567890/path?query").unwrap().to_string(), "did:example:1234567890/path?query");
583 assert_eq!(DIDUrl::parse("did:example:1234567890/path#fragment").unwrap().to_string(), "did:example:1234567890/path#fragment");
584 assert_eq!(DIDUrl::parse("did:example:1234567890?query#fragment").unwrap().to_string(), "did:example:1234567890?query#fragment");
585
586 let did_url = DIDUrl::parse("did:example:1234567890/path?query#fragment").unwrap();
587 assert!(!did_url.url().is_empty());
588 assert_eq!(did_url.to_string(), "did:example:1234567890/path?query#fragment");
589 assert_eq!(did_url.path().unwrap(), "/path");
590 assert_eq!(did_url.query().unwrap(), "query");
591 assert_eq!(did_url.fragment().unwrap(), "fragment");
592 }
593
594 #[rustfmt::skip]
595 #[test]
596 fn test_join_valid() {
597 let did_url = DIDUrl::parse("did:example:1234567890").unwrap();
598 assert_eq!(did_url.join("/path").unwrap().to_string(), "did:example:1234567890/path");
599 assert_eq!(did_url.join("?query").unwrap().to_string(), "did:example:1234567890?query");
600 assert_eq!(did_url.join("#fragment").unwrap().to_string(), "did:example:1234567890#fragment");
601
602 assert_eq!(did_url.join("/path?query").unwrap().to_string(), "did:example:1234567890/path?query");
603 assert_eq!(did_url.join("/path#fragment").unwrap().to_string(), "did:example:1234567890/path#fragment");
604 assert_eq!(did_url.join("?query#fragment").unwrap().to_string(), "did:example:1234567890?query#fragment");
605
606 let did_url = did_url.join("/path?query#fragment").unwrap();
607 assert_eq!(did_url.to_string(), "did:example:1234567890/path?query#fragment");
608 assert_eq!(did_url.path().unwrap(), "/path");
609 assert_eq!(did_url.query().unwrap(), "query");
610 assert_eq!(did_url.fragment().unwrap(), "fragment");
611 }
612
613 #[test]
614 fn test_did_url_invalid() {
615 assert!(DIDUrl::parse("did:example:1234567890/invalid{path}").is_err());
616 assert!(DIDUrl::parse("did:example:1234567890?invalid{query}").is_err());
617 assert!(DIDUrl::parse("did:example:1234567890#invalid{fragment}").is_err());
618
619 let did_url = DIDUrl::parse("did:example:1234567890").unwrap();
620 assert!(did_url.join("noleadingdelimiter").is_err());
621 assert!(did_url.join("/invalid{path}").is_err());
622 assert!(did_url.join("?invalid{query}").is_err());
623 assert!(did_url.join("#invalid{fragment}").is_err());
624 }
625
626 #[test]
627 fn test_did_url_basic_comparisons() {
628 let did_url1 = DIDUrl::parse("did:example:1234567890").unwrap();
629 let did_url1_copy = DIDUrl::parse("did:example:1234567890").unwrap();
630 assert_eq!(did_url1, did_url1_copy);
631
632 let did_url2 = DIDUrl::parse("did:example:0987654321").unwrap();
633 assert_ne!(did_url1, did_url2);
634 assert!(did_url1 > did_url2);
635
636 let did_url3 = DIDUrl::parse("did:fxample:1234567890").unwrap();
637 assert_ne!(did_url1, did_url3);
638 assert!(did_url1 < did_url3);
639
640 let did_url4 = DIDUrl::parse("did:example:1234567890/path").unwrap();
641 assert_ne!(did_url1, did_url4);
642 assert_ne!(did_url1.url(), did_url4.url());
643 assert_eq!(did_url1.did(), did_url4.did());
644 assert!(did_url1 < did_url4);
645
646 let did_url5 = DIDUrl::parse("did:example:1234567890/zero").unwrap();
647 assert_ne!(did_url4, did_url5);
648 assert_ne!(did_url4.url(), did_url5.url());
649 assert_eq!(did_url4.did(), did_url5.did());
650 assert!(did_url4 < did_url5);
651 }
652
653 #[test]
654 fn test_path_valid() {
655 let mut relative_url = RelativeDIDUrl::new();
656
657 assert!(relative_url.set_path(Some("/path")).is_ok());
659 assert_eq!(relative_url.path().unwrap(), "/path");
660 assert!(relative_url.set_path(Some("/path/sub-path/resource")).is_ok());
661 assert_eq!(relative_url.path().unwrap(), "/path/sub-path/resource");
662
663 assert!(relative_url.set_path(Some("")).is_ok());
665 assert!(relative_url.path().is_none());
666 assert!(relative_url.set_path(None).is_ok());
667 assert!(relative_url.path().is_none());
668
669 assert!(relative_url.set_path(Some("/p%AAth")).is_ok());
671 assert_eq!(relative_url.path().unwrap(), "/p%AAth");
672 }
673
674 #[rustfmt::skip]
675 #[test]
676 fn test_path_invalid() {
677 let mut relative_url = RelativeDIDUrl::new();
678
679 assert!(matches!(relative_url.set_path(Some("/white space")), Err(Error::InvalidPath)));
681 assert!(matches!(relative_url.set_path(Some("/white\tspace")), Err(Error::InvalidPath)));
682 assert!(matches!(relative_url.set_path(Some("/white\nspace")), Err(Error::InvalidPath)));
683 assert!(matches!(relative_url.set_path(Some("/path{invalid_brackets}")), Err(Error::InvalidPath)));
684
685 assert!(matches!(relative_url.set_path(Some("path")), Err(Error::InvalidPath)));
687 assert!(matches!(relative_url.set_path(Some("p/")), Err(Error::InvalidPath)));
688 assert!(matches!(relative_url.set_path(Some("p/ath")), Err(Error::InvalidPath)));
689 assert!(matches!(relative_url.set_path(Some("path/")), Err(Error::InvalidPath)));
690 assert!(matches!(relative_url.set_path(Some("path/sub-path/")), Err(Error::InvalidPath)));
691
692 assert!(matches!(relative_url.set_path(Some("?query")), Err(Error::InvalidPath)));
694 assert!(matches!(relative_url.set_path(Some("some?query")), Err(Error::InvalidPath)));
695 assert!(matches!(relative_url.set_path(Some("/path?")), Err(Error::InvalidPath)));
696 assert!(matches!(relative_url.set_path(Some("/path?query")), Err(Error::InvalidPath)));
697 assert!(matches!(relative_url.set_path(Some("/path/query?")), Err(Error::InvalidPath)));
698
699 assert!(matches!(relative_url.set_path(Some("#fragment")), Err(Error::InvalidPath)));
701 assert!(matches!(relative_url.set_path(Some("some#fragment")), Err(Error::InvalidPath)));
702 assert!(matches!(relative_url.set_path(Some("/path#")), Err(Error::InvalidPath)));
703 assert!(matches!(relative_url.set_path(Some("/path#fragment")), Err(Error::InvalidPath)));
704 assert!(matches!(relative_url.set_path(Some("/path/fragment#")), Err(Error::InvalidPath)));
705 }
706
707 #[test]
708 fn test_query_valid() {
709 let mut relative_url = RelativeDIDUrl::new();
710
711 assert!(relative_url.set_query(Some("")).is_ok());
713 assert!(relative_url.query().is_none());
714
715 assert!(relative_url.set_query(Some("?query")).is_ok());
717 assert_eq!(relative_url.query().unwrap(), "query");
718 assert!(relative_url.set_query(Some("?name=value")).is_ok());
719 assert_eq!(relative_url.query().unwrap(), "name=value");
720 assert!(relative_url.set_query(Some("?name=value&name2=value2")).is_ok());
721 assert_eq!(relative_url.query().unwrap(), "name=value&name2=value2");
722 assert!(relative_url.set_query(Some("?name=value&name2=value2&3=true")).is_ok());
723 assert_eq!(relative_url.query().unwrap(), "name=value&name2=value2&3=true");
724
725 assert!(relative_url.set_query(Some("query")).is_ok());
727 assert_eq!(relative_url.query().unwrap(), "query");
728 assert!(relative_url.set_query(Some("name=value&name2=value2&3=true")).is_ok());
729 assert_eq!(relative_url.query().unwrap(), "name=value&name2=value2&3=true");
730
731 assert!(relative_url.set_query(Some("qu%EEry")).is_ok());
733 assert_eq!(relative_url.query().unwrap(), "qu%EEry");
734 }
735
736 #[rustfmt::skip]
737 #[test]
738 fn test_query_invalid() {
739 let mut relative_url = RelativeDIDUrl::new();
740
741 assert!(matches!(relative_url.set_query(Some("?")), Err(Error::InvalidQuery)));
743
744 assert!(matches!(relative_url.set_query(Some("?white space")), Err(Error::InvalidQuery)));
746 assert!(matches!(relative_url.set_query(Some("?white\tspace")), Err(Error::InvalidQuery)));
747 assert!(matches!(relative_url.set_query(Some("?white\nspace")), Err(Error::InvalidQuery)));
748 assert!(matches!(relative_url.set_query(Some("?query{invalid_brackets}")), Err(Error::InvalidQuery)));
749
750 assert!(matches!(relative_url.set_query(Some("#fragment")), Err(Error::InvalidQuery)));
752 assert!(matches!(relative_url.set_query(Some("some#fragment")), Err(Error::InvalidQuery)));
753 assert!(matches!(relative_url.set_query(Some("?query#fragment")), Err(Error::InvalidQuery)));
754 assert!(matches!(relative_url.set_query(Some("?query=a#fragment")), Err(Error::InvalidQuery)));
755 assert!(matches!(relative_url.set_query(Some("?query=#fragment")), Err(Error::InvalidQuery)));
756 assert!(matches!(relative_url.set_query(Some("?query=frag#ment")), Err(Error::InvalidQuery)));
757 assert!(matches!(relative_url.set_query(Some("?query=fragment#")), Err(Error::InvalidQuery)));
758 }
759
760 #[rustfmt::skip]
761 #[test]
762 fn test_fragment_valid() {
763 let mut relative_url = RelativeDIDUrl::new();
764
765 assert!(relative_url.set_fragment(Some("#fragment")).is_ok());
767 assert_eq!(relative_url.fragment().unwrap(), "fragment");
768 assert!(relative_url.set_fragment(Some("#longer_fragment?and/other-delimiters:valid")).is_ok());
769 assert_eq!(relative_url.fragment().unwrap(), "longer_fragment?and/other-delimiters:valid");
770
771 assert!(relative_url.set_fragment(Some("fragment")).is_ok());
773 assert_eq!(relative_url.fragment().unwrap(), "fragment");
774 assert!(relative_url.set_fragment(Some("longer_fragment?and/other-delimiters:valid")).is_ok());
775 assert_eq!(relative_url.fragment().unwrap(), "longer_fragment?and/other-delimiters:valid");
776
777 assert!(relative_url.set_fragment(Some("")).is_ok());
779 assert!(relative_url.fragment().is_none());
780 assert!(relative_url.set_fragment(None).is_ok());
781 assert!(relative_url.fragment().is_none());
782
783 assert!(relative_url.set_fragment(Some("fr%AAgm%EEnt")).is_ok());
785 assert_eq!(relative_url.fragment().unwrap(), "fr%AAgm%EEnt");
786 }
787
788 #[rustfmt::skip]
789 #[test]
790 fn test_fragment_invalid() {
791 let mut relative_url = RelativeDIDUrl::new();
792
793 assert!(matches!(relative_url.set_fragment(Some("#")), Err(Error::InvalidFragment)));
795
796 assert!(matches!(relative_url.set_fragment(Some("#white space")), Err(Error::InvalidFragment)));
798 assert!(matches!(relative_url.set_fragment(Some("#white\tspace")), Err(Error::InvalidFragment)));
799 assert!(matches!(relative_url.set_fragment(Some("#white\nspace")), Err(Error::InvalidFragment)));
800 assert!(matches!(relative_url.set_fragment(Some("#fragment{invalid_brackets}")), Err(Error::InvalidFragment)));
801 assert!(matches!(relative_url.set_fragment(Some("#fragment\"other\"")), Err(Error::InvalidFragment)));
802 }
803
804 proptest::proptest! {
805 #[test]
806 fn test_fuzz_join_no_panic(s in "\\PC*") {
807 let did_url = DIDUrl::parse("did:example:1234567890").unwrap();
808 let _ = did_url.join(s);
809 }
810
811 #[test]
812 fn test_fuzz_path_no_panic(s in "\\PC*") {
813 let mut url = RelativeDIDUrl::new();
814 let _ = url.set_path(Some(&s));
815 }
816
817 #[test]
818 fn test_fuzz_query_no_panic(s in "\\PC*") {
819 let mut url = RelativeDIDUrl::new();
820 let _ = url.set_query(Some(&s));
821 }
822
823 #[test]
824 fn test_fuzz_fragment_no_panic(s in "\\PC*") {
825 let mut url = RelativeDIDUrl::new();
826 let _ = url.set_fragment(Some(&s));
827 }
828 }
829}