1use crate::{
2 builder::{
3 state::{NonRefStart, Start},
4 Builder,
5 },
6 component::{Authority, IAuthority, Scheme},
7 encoding::{encode_byte, encoder::*, EStr, Encoder},
8 error::{ParseError, ResolveError},
9 internal::{Criteria, HostMeta, Meta, Parse, RiRef, Value},
10 normalizer, parser, resolver,
11};
12use alloc::{borrow::ToOwned, string::String};
13use borrow_or_share::{BorrowOrShare, Bos};
14use core::{
15 borrow::Borrow,
16 cmp::Ordering,
17 fmt, hash,
18 num::NonZeroUsize,
19 str::{self, FromStr},
20};
21
22#[cfg(feature = "serde")]
23use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
24
25macro_rules! cond {
26 (if true { $($then:tt)* } else { $($else:tt)* }) => { $($then)* };
27 (if false { $($then:tt)* } else { $($else:tt)* }) => { $($else)* };
28}
29
30macro_rules! ri_maybe_ref {
31 (
32 Type = $Ty:ident,
33 type_name = $ty:literal,
34 variable_name = $var:literal,
35 name = $name:literal,
36 indefinite_article = $art:literal,
37 description = $desc:literal,
38 must_be_ascii = $must_be_ascii:literal,
39 must_have_scheme = $must_have_scheme:tt,
40 rfc = $rfc:literal,
41 abnf_rule = ($abnf:literal, $abnf_link:literal),
42 $(
43 NonRefType = $NonRefTy:ident,
44 non_ref_name = $nr_name:literal,
45 non_ref_link = $nr_link:literal,
46 abnf_rule_absolute = ($abnf_abs:literal, $abnf_abs_link:literal),
47 )?
48 $(
49 RefType = $RefTy:ident,
50 ref_name = $ref_name:literal,
51 )?
52 AuthorityType = $Authority:ident,
53 UserinfoEncoderType = $UserinfoE:ident,
54 RegNameEncoderType = $RegNameE:ident,
55 PathEncoderType = $PathE:ident,
56 QueryEncoderType = $QueryE:ident,
57 FragmentEncoderType = $FragmentE:ident,
58 ) => {
59 #[doc = $desc]
60 #[doc = concat!("Two variants of `", $ty, "` are available: ")]
66 #[doc = concat!("`", $ty, "<&str>` (borrowed) and `", $ty, "<String>` (owned).")]
67 #[doc = concat!("`", $ty, "<&'a str>`")]
69 #[doc = concat!("use fluent_uri::", $ty, ";")]
74 #[doc = concat!("// Keep a reference to the path after dropping the `", $ty, "`.")]
76 #[doc = concat!("let path = ", $ty, "::parse(\"foo:bar\")?.path();")]
77 #[doc = concat!("`", $ty, "`s")]
84 #[doc = concat!($art, " ", $name, ":")]
91 #[doc = concat!(" ", $ty, ",")]
97 #[doc = concat!("let ", $var, " = ", $ty, "::parse(s)?;")]
103 #[doc = concat!("assert_eq!(", $var, ".scheme()",
105 cond!(if $must_have_scheme { "" } else { ".unwrap()" }), ", SCHEME_FOO);")]
106 #[doc = concat!("let auth = ", $var, ".authority().unwrap();")]
108 #[doc = concat!("assert_eq!(", $var, ".path(), \"/over/there\");")]
116 #[doc = concat!("assert_eq!(", $var, ".query().unwrap(), \"name=ferret\");")]
117 #[doc = concat!("assert_eq!(", $var, ".fragment().unwrap(), \"nose\");")]
118 #[doc = concat!("`", $ty, "<&str>` and `", $ty, "<String>`:")]
123 #[doc = concat!("use fluent_uri::", $ty, ";")]
126 #[doc = concat!("// Parse into a `", $ty, "<&str>` from a string slice.")]
130 #[doc = concat!("let ", $var, ": ", $ty, "<&str> = ", $ty, "::parse(s)?;")]
131 #[doc = concat!("// Parse into a `", $ty, "<String>` from an owned string.")]
133 #[doc = concat!("let ", $var, "_owned: ", $ty, "<String> = ", $ty, "::parse(s.to_owned()).map_err(|e| e.strip_input())?;")]
134 #[doc = concat!("// Convert a `", $ty, "<&str>` to `", $ty, "<String>`.")]
136 #[doc = concat!("let ", $var, "_owned: ", $ty, "<String> = ", $var, ".to_owned();")]
137 #[doc = concat!("// Borrow a `", $ty, "<String>` as `", $ty, "<&str>`.")]
139 #[doc = concat!("let ", $var, ": ", $ty, "<&str> = ", $var, "_owned.borrow();")]
140 #[derive(Clone, Copy)]
143 pub struct $Ty<T> {
144 val: T,
146 meta: Meta,
149 }
150
151 impl<T> RiRef for $Ty<T> {
152 type Val = T;
153 type UserinfoE = $UserinfoE;
154 type RegNameE = $RegNameE;
155 type PathE = $PathE;
156 type QueryE = $QueryE;
157 type FragmentE = $FragmentE;
158
159 fn new(val: T, meta: Meta) -> Self {
160 Self { val, meta }
161 }
162
163 fn criteria() -> Criteria {
164 Criteria {
165 must_be_ascii: $must_be_ascii,
166 must_have_scheme: $must_have_scheme,
167 }
168 }
169 }
170
171 impl<T> $Ty<T> {
172 #[doc = concat!("Parses ", $art, " ", $name, " from a string into ", $art, " `", $ty, "`.")]
173 #[doc = concat!("- `Result<", $ty, "<&str>, ParseError>` for `I = &str`;")]
177 #[doc = concat!("- `Result<", $ty, "<String>, ParseError<String>>` for `I = String`.")]
178 #[doc = concat!("[`", $abnf, "`][abnf] ABNF rule from RFC ", $rfc, ".")]
183 #[doc = concat!("[abnf]: ", $abnf_link)]
188 pub fn parse<I>(input: I) -> Result<Self, I::Err>
191 where
192 I: Parse<Val = T>,
193 {
194 input.parse()
195 }
196 }
197
198 impl $Ty<String> {
199 #[doc = concat!("Creates a new builder for ", $name, ".")]
200 #[inline]
201 pub fn builder() -> Builder<Self, cond!(if $must_have_scheme { NonRefStart } else { Start })> {
202 Builder::new()
203 }
204
205 #[doc = concat!("Borrows this `", $ty, "<String>` as `", $ty, "<&str>`.")]
206 #[allow(clippy::should_implement_trait)]
207 #[inline]
208 #[must_use]
209 pub fn borrow(&self) -> $Ty<&str> {
210 $Ty {
211 val: &self.val,
212 meta: self.meta,
213 }
214 }
215
216 #[doc = concat!("Consumes this `", $ty, "<String>` and yields the underlying [`String`].")]
217 #[inline]
218 #[must_use]
219 pub fn into_string(self) -> String {
220 self.val
221 }
222 }
223
224 impl $Ty<&str> {
225 #[doc = concat!("Creates a new `", $ty, "<String>` by cloning the contents of this `", $ty, "<&str>`.")]
226 #[inline]
227 #[must_use]
228 pub fn to_owned(&self) -> $Ty<String> {
229 $Ty {
230 val: self.val.to_owned(),
231 meta: self.meta,
232 }
233 }
234 }
235
236 impl<'i, 'o, T: BorrowOrShare<'i, 'o, str>> $Ty<T> {
237 #[doc = concat!("Returns the ", $name, " as a string slice.")]
238 #[must_use]
239 pub fn as_str(&'i self) -> &'o str {
240 self.val.borrow_or_share()
241 }
242
243 fn as_ref(&'i self) -> Ref<'o, 'i> {
244 Ref::new(self.as_str(), &self.meta)
245 }
246
247 cond!(if $must_have_scheme {
248 #[doc = concat!("use fluent_uri::{component::Scheme, ", $ty, "};")]
259 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
263 #[doc = concat!("assert_eq!(", $var, ".scheme(), SCHEME_HTTP);")]
264 #[must_use]
267 pub fn scheme(&'i self) -> &'o Scheme {
268 self.as_ref().scheme()
269 }
270 } else {
271 #[doc = concat!("use fluent_uri::{component::Scheme, ", $ty, "};")]
282 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
286 #[doc = concat!("assert_eq!(", $var, ".scheme(), Some(SCHEME_HTTP));")]
287 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"/path/to/file\")?;")]
289 #[doc = concat!("assert_eq!(", $var, ".scheme(), None);")]
290 #[must_use]
293 pub fn scheme(&'i self) -> Option<&'o Scheme> {
294 self.as_ref().scheme_opt()
295 }
296 });
297
298 #[doc = concat!("use fluent_uri::", $ty, ";")]
306 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
308 #[doc = concat!("assert!(", $var, ".authority().is_some());")]
309 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"mailto:user@example.com\")?;")]
311 #[doc = concat!("assert!(", $var, ".authority().is_none());")]
312 #[must_use]
315 pub fn authority(&'i self) -> Option<$Authority<'o>> {
316 self.as_ref().authority().map(Authority::cast)
317 }
318
319 #[doc = concat!("use fluent_uri::", $ty, ";")]
332 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
334 #[doc = concat!("assert_eq!(", $var, ".path(), \"/\");")]
335 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"mailto:user@example.com\")?;")]
337 #[doc = concat!("assert_eq!(", $var, ".path(), \"user@example.com\");")]
338 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com\")?;")]
340 #[doc = concat!("assert_eq!(", $var, ".path(), \"\");")]
341 #[must_use]
344 pub fn path(&'i self) -> &'o EStr<$PathE> {
345 self.as_ref().path().cast()
346 }
347
348 #[doc = concat!("use fluent_uri::{encoding::EStr, ", $ty, "};")]
356 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/?lang=en\")?;")]
358 #[doc = concat!("assert_eq!(", $var, ".query(), Some(EStr::new_or_panic(\"lang=en\")));")]
359 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"ftp://192.0.2.1/\")?;")]
361 #[doc = concat!("assert_eq!(", $var, ".query(), None);")]
362 #[must_use]
365 pub fn query(&'i self) -> Option<&'o EStr<$QueryE>> {
366 self.as_ref().query().map(EStr::cast)
367 }
368
369 #[doc = concat!("use fluent_uri::{encoding::EStr, ", $ty, "};")]
377 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/#usage\")?;")]
379 #[doc = concat!("assert_eq!(", $var, ".fragment(), Some(EStr::new_or_panic(\"usage\")));")]
380 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"ftp://192.0.2.1/\")?;")]
382 #[doc = concat!("assert_eq!(", $var, ".fragment(), None);")]
383 #[must_use]
386 pub fn fragment(&'i self) -> Option<&'o EStr<$FragmentE>> {
387 self.as_ref().fragment().map(EStr::cast)
388 }
389 }
390
391 impl<'i, 'o, T: Bos<str>> $Ty<T> {
392 $(
393 #[doc = concat!("Resolves the ", $name, " against the given base ", $nr_name)]
394 #[doc = concat!("and returns the target ", $nr_name, ".")]
395 #[doc = concat!("The base ", $nr_name)]
397 #[doc = concat!("[`", $abnf_abs, "`][abnf] ABNF rule from RFC ", $rfc, ".")]
399 #[doc = concat!("To prepare a base ", $nr_name, ",")]
401 #[doc = concat!("from any ", $nr_name, ".")]
403 #[doc = concat!("[abnf]: ", $abnf_abs_link)]
429 #[doc = concat!("[`with_fragment`]: ", stringify!($NonRefTy), "::with_fragment")]
430 #[doc = concat!("[`set_fragment`]: ", stringify!($NonRefTy), "::set_fragment")]
431 #[doc = concat!("use fluent_uri::{", stringify!($NonRefTy), ", ", $ty, "};")]
446 #[doc = concat!("let base = ", stringify!($NonRefTy), "::parse(\"http://example.com/foo/bar\")?;")]
448 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"baz\")?;")]
450 #[doc = concat!("assert_eq!(", $var, ".resolve_against(&base).unwrap(), \"http://example.com/foo/baz\");")]
451 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"../baz\")?;")]
453 #[doc = concat!("assert_eq!(", $var, ".resolve_against(&base).unwrap(), \"http://example.com/baz\");")]
454 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"?baz\")?;")]
456 #[doc = concat!("assert_eq!(", $var, ".resolve_against(&base).unwrap(), \"http://example.com/foo/bar?baz\");")]
457 pub fn resolve_against<U: Bos<str>>(
460 &self,
461 base: &$NonRefTy<U>,
462 ) -> Result<$NonRefTy<String>, ResolveError> {
463 resolver::resolve(base.as_ref(), self.as_ref()).map(RiRef::from_pair)
464 }
465 )?
466
467 #[doc = concat!("Normalizes the ", $name, ".")]
468 #[doc = concat!("use fluent_uri::", $ty, ";")]
495 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"eXAMPLE://a/./b/../b/%63/%7bfoo%7d\")?;")]
497 #[doc = concat!("assert_eq!(", $var, ".normalize(), \"example://a/b/c/%7Bfoo%7D\");")]
498 #[must_use]
501 pub fn normalize(&self) -> $Ty<String> {
502 RiRef::from_pair(normalizer::normalize(self.as_ref(), $must_be_ascii))
503 }
504
505 cond!(if $must_have_scheme {} else {
506 #[doc = concat!("use fluent_uri::", $ty, ";")]
512 #[doc = concat!("assert!(", $ty, "::parse(\"http://example.com/\")?.has_scheme());")]
514 #[doc = concat!("assert!(!", $ty, "::parse(\"/path/to/file\")?.has_scheme());")]
515 #[must_use]
518 pub fn has_scheme(&self) -> bool {
519 self.as_ref().has_scheme()
520 }
521 });
522
523 #[doc = concat!("use fluent_uri::", $ty, ";")]
529 #[doc = concat!("assert!(", $ty, "::parse(\"http://example.com/\")?.has_authority());")]
531 #[doc = concat!("assert!(!", $ty, "::parse(\"mailto:user@example.com\")?.has_authority());")]
532 #[must_use]
535 pub fn has_authority(&self) -> bool {
536 self.as_ref().has_authority()
537 }
538
539 #[doc = concat!("use fluent_uri::", $ty, ";")]
545 #[doc = concat!("assert!(", $ty, "::parse(\"http://example.com/?lang=en\")?.has_query());")]
547 #[doc = concat!("assert!(!", $ty, "::parse(\"ftp://192.0.2.1/\")?.has_query());")]
548 #[must_use]
551 pub fn has_query(&self) -> bool {
552 self.as_ref().has_query()
553 }
554
555 #[doc = concat!("use fluent_uri::", $ty, ";")]
561 #[doc = concat!("assert!(", $ty, "::parse(\"http://example.com/#usage\")?.has_fragment());")]
563 #[doc = concat!("assert!(!", $ty, "::parse(\"ftp://192.0.2.1/\")?.has_fragment());")]
564 #[must_use]
567 pub fn has_fragment(&self) -> bool {
568 self.as_ref().has_fragment()
569 }
570
571 #[doc = concat!("Creates a new ", $name)]
572 #[doc = concat!("use fluent_uri::{encoding::EStr, ", $ty, "};")]
580 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
582 #[doc = concat!(" ", $var, ".with_fragment(Some(EStr::new_or_panic(\"fragment\"))),")]
584 #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/#fragment\")?;")]
588 #[doc = concat!(" ", $var, ".with_fragment(None),")]
590 #[must_use]
595 pub fn with_fragment(&self, opt: Option<&EStr<$FragmentE>>) -> $Ty<String> {
596 RiRef::new(self.as_ref().with_fragment(opt.map(EStr::as_str)), self.meta)
598 }
599 }
600
601 impl $Ty<String> {
602 #[doc = concat!("use fluent_uri::{encoding::EStr, ", $ty, "};")]
610 #[doc = concat!("let mut ", $var, " = ", $ty, "::parse(\"http://example.com/\")?.to_owned();")]
612 #[doc = concat!($var, ".set_fragment(Some(EStr::new_or_panic(\"fragment\")));")]
614 #[doc = concat!("assert_eq!(", $var, ", \"http://example.com/#fragment\");")]
615 #[doc = concat!($var, ".set_fragment(None);")]
617 #[doc = concat!("assert_eq!(", $var, ", \"http://example.com/\");")]
618 pub fn set_fragment(&mut self, opt: Option<&EStr<$FragmentE>>) {
621 Ref::set_fragment(&mut self.val, &self.meta, opt.map(EStr::as_str))
622 }
623 }
624
625 impl<T: Value> Default for $Ty<T> {
626 #[doc = concat!("Creates an empty ", $name, ".")]
627 fn default() -> Self {
628 Self {
629 val: T::default(),
630 meta: Meta::default(),
631 }
632 }
633 }
634
635 impl<T: Bos<str>, U: Bos<str>> PartialEq<$Ty<U>> for $Ty<T> {
636 fn eq(&self, other: &$Ty<U>) -> bool {
637 self.as_str() == other.as_str()
638 }
639 }
640
641 impl<T: Bos<str>> PartialEq<str> for $Ty<T> {
642 fn eq(&self, other: &str) -> bool {
643 self.as_str() == other
644 }
645 }
646
647 impl<T: Bos<str>> PartialEq<$Ty<T>> for str {
648 fn eq(&self, other: &$Ty<T>) -> bool {
649 self == other.as_str()
650 }
651 }
652
653 impl<T: Bos<str>> PartialEq<&str> for $Ty<T> {
654 fn eq(&self, other: &&str) -> bool {
655 self.as_str() == *other
656 }
657 }
658
659 impl<T: Bos<str>> PartialEq<$Ty<T>> for &str {
660 fn eq(&self, other: &$Ty<T>) -> bool {
661 *self == other.as_str()
662 }
663 }
664
665 impl<T: Bos<str>> Eq for $Ty<T> {}
666
667 impl<T: Bos<str>> hash::Hash for $Ty<T> {
668 fn hash<H: hash::Hasher>(&self, state: &mut H) {
669 self.as_str().hash(state);
670 }
671 }
672
673 impl<T: Bos<str>> PartialOrd for $Ty<T> {
674 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
675 Some(self.cmp(other))
676 }
677 }
678
679 impl<T: Bos<str>> Ord for $Ty<T> {
680 fn cmp(&self, other: &Self) -> Ordering {
681 self.as_str().cmp(other.as_str())
682 }
683 }
684
685 impl<T: Bos<str>> AsRef<str> for $Ty<T> {
686 fn as_ref(&self) -> &str {
687 self.as_str()
688 }
689 }
690
691 impl<T: Bos<str>> Borrow<str> for $Ty<T> {
692 fn borrow(&self) -> &str {
693 self.as_str()
694 }
695 }
696
697 impl<'a> TryFrom<&'a str> for $Ty<&'a str> {
698 type Error = ParseError;
699
700 #[inline]
702 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
703 $Ty::parse(value)
704 }
705 }
706
707 impl TryFrom<String> for $Ty<String> {
708 type Error = ParseError<String>;
709
710 #[inline]
712 fn try_from(value: String) -> Result<Self, Self::Error> {
713 $Ty::parse(value)
714 }
715 }
716
717 impl<'a> From<$Ty<&'a str>> for &'a str {
718 #[doc = concat!("Equivalent to [`as_str`](", $ty, "::as_str).")]
719 #[inline]
720 fn from(value: $Ty<&'a str>) -> &'a str {
721 value.val
722 }
723 }
724
725 impl<'a> From<$Ty<String>> for String {
726 #[doc = concat!("Equivalent to [`into_string`](", $ty, "::into_string).")]
727 #[inline]
728 fn from(value: $Ty<String>) -> String {
729 value.val
730 }
731 }
732
733 impl From<$Ty<&str>> for $Ty<String> {
734 #[inline]
736 fn from(value: $Ty<&str>) -> Self {
737 value.to_owned()
738 }
739 }
740
741 impl FromStr for $Ty<String> {
742 type Err = ParseError;
743
744 #[doc = concat!("Equivalent to `", $ty, "::parse(s).map(|r| r.to_owned())`.")]
745 #[inline]
746 fn from_str(s: &str) -> Result<Self, Self::Err> {
747 $Ty::parse(s).map(|r| r.to_owned())
748 }
749 }
750
751 impl<T: Bos<str>> fmt::Debug for $Ty<T> {
752 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
753 f.debug_struct($ty)
754 .field("scheme", &self.scheme())
755 .field("authority", &self.authority())
756 .field("path", &self.path())
757 .field("query", &self.query())
758 .field("fragment", &self.fragment())
759 .finish()
760 }
761 }
762
763 impl<T: Bos<str>> fmt::Display for $Ty<T> {
764 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
765 fmt::Display::fmt(self.as_str(), f)
766 }
767 }
768
769 #[cfg(feature = "serde")]
770 impl<T: Bos<str>> Serialize for $Ty<T> {
771 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
772 where
773 S: Serializer,
774 {
775 serializer.serialize_str(self.as_str())
776 }
777 }
778
779 #[cfg(feature = "serde")]
780 impl<'de> Deserialize<'de> for $Ty<&'de str> {
781 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
782 where
783 D: Deserializer<'de>,
784 {
785 let s = <&str>::deserialize(deserializer)?;
786 $Ty::parse(s).map_err(de::Error::custom)
787 }
788 }
789
790 #[cfg(feature = "serde")]
791 impl<'de> Deserialize<'de> for $Ty<String> {
792 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
793 where
794 D: Deserializer<'de>,
795 {
796 let s = String::deserialize(deserializer)?;
797 $Ty::parse(s).map_err(de::Error::custom)
798 }
799 }
800 };
801}
802
803#[derive(Clone, Copy)]
805pub struct Ref<'v, 'm> {
806 val: &'v str,
807 meta: &'m Meta,
808}
809
810impl<'v, 'm> Ref<'v, 'm> {
811 pub fn new(val: &'v str, meta: &'m Meta) -> Self {
812 Self { val, meta }
813 }
814
815 pub fn as_str(self) -> &'v str {
816 self.val
817 }
818
819 fn slice(self, start: usize, end: usize) -> &'v str {
820 &self.val[start..end]
821 }
822
823 fn eslice<E: Encoder>(self, start: usize, end: usize) -> &'v EStr<E> {
824 EStr::new_validated(self.slice(start, end))
825 }
826
827 pub fn scheme_opt(self) -> Option<&'v Scheme> {
828 let end = self.meta.scheme_end?.get();
829 Some(Scheme::new_validated(self.slice(0, end)))
830 }
831
832 pub fn scheme(self) -> &'v Scheme {
833 let end = self.meta.scheme_end.map_or(0, |i| i.get());
834 Scheme::new_validated(self.slice(0, end))
835 }
836
837 pub fn authority(self) -> Option<IAuthority<'v>> {
838 let mut meta = self.meta.auth_meta?;
839 let start = match self.meta.scheme_end {
840 Some(i) => i.get() + 3,
841 None => 2,
842 };
843 let end = self.meta.path_bounds.0;
844
845 meta.host_bounds.0 -= start;
846 meta.host_bounds.1 -= start;
847
848 Some(IAuthority::new(self.slice(start, end), meta))
849 }
850
851 pub fn path(self) -> &'v EStr<IPath> {
852 self.eslice(self.meta.path_bounds.0, self.meta.path_bounds.1)
853 }
854
855 pub fn query(self) -> Option<&'v EStr<IQuery>> {
856 let end = self.meta.query_end?.get();
857 Some(self.eslice(self.meta.path_bounds.1 + 1, end))
858 }
859
860 fn fragment_start(self) -> Option<usize> {
861 Some(self.meta.query_or_path_end())
862 .filter(|&i| i != self.val.len())
863 .map(|i| i + 1)
864 }
865
866 pub fn fragment(self) -> Option<&'v EStr<IFragment>> {
867 self.fragment_start()
868 .map(|i| self.eslice(i, self.val.len()))
869 }
870
871 pub fn set_fragment(buf: &mut String, meta: &Meta, opt: Option<&str>) {
872 buf.truncate(meta.query_or_path_end());
873 if let Some(s) = opt {
874 buf.reserve(s.len() + 1);
875 buf.push('#');
876 buf.push_str(s);
877 }
878 }
879
880 pub fn with_fragment(self, opt: Option<&str>) -> String {
881 let stripped_len = self.meta.query_or_path_end();
882 let additional_len = opt.map_or(0, |s| s.len() + 1);
883
884 let mut buf = String::with_capacity(stripped_len + additional_len);
885 buf.push_str(&self.val[..stripped_len]);
886 if let Some(s) = opt {
887 buf.push('#');
888 buf.push_str(s);
889 }
890 buf
891 }
892
893 #[inline]
894 pub fn has_scheme(self) -> bool {
895 self.meta.scheme_end.is_some()
896 }
897
898 #[inline]
899 pub fn has_authority(self) -> bool {
900 self.meta.auth_meta.is_some()
901 }
902
903 #[inline]
904 pub fn has_query(self) -> bool {
905 self.meta.query_end.is_some()
906 }
907
908 #[inline]
909 pub fn has_fragment(self) -> bool {
910 self.meta.query_or_path_end() != self.val.len()
911 }
912
913 pub fn ensure_has_scheme(self) -> Result<(), ParseError> {
914 if self.has_scheme() {
915 Ok(())
916 } else {
917 parser::err!(0, NoScheme);
918 }
919 }
920
921 pub fn ensure_ascii(self) -> Result<(), ParseError> {
922 if let Some(pos) = self.as_str().bytes().position(|x| !x.is_ascii()) {
923 parser::err!(pos, UnexpectedChar);
924 } else {
925 Ok(())
926 }
927 }
928}
929
930ri_maybe_ref! {
931 Type = Uri,
932 type_name = "Uri",
933 variable_name = "uri",
934 name = "URI",
935 indefinite_article = "a",
936 description = "A URI.",
937 must_be_ascii = true,
938 must_have_scheme = true,
939 rfc = 3986,
940 abnf_rule = ("URI", "https://datatracker.ietf.org/doc/html/rfc3986#section-3"),
941 RefType = UriRef,
942 ref_name = "URI reference",
943 AuthorityType = Authority,
944 UserinfoEncoderType = Userinfo,
945 RegNameEncoderType = RegName,
946 PathEncoderType = Path,
947 QueryEncoderType = Query,
948 FragmentEncoderType = Fragment,
949}
950
951ri_maybe_ref! {
952 Type = UriRef,
953 type_name = "UriRef",
954 variable_name = "uri_ref",
955 name = "URI reference",
956 indefinite_article = "a",
957 description = "A URI reference, i.e., either a URI or a relative reference.",
958 must_be_ascii = true,
959 must_have_scheme = false,
960 rfc = 3986,
961 abnf_rule = ("URI-reference", "https://datatracker.ietf.org/doc/html/rfc3986#section-4.1"),
962 NonRefType = Uri,
963 non_ref_name = "URI",
964 non_ref_link = "https://datatracker.ietf.org/doc/html/rfc3986#section-3",
965 abnf_rule_absolute = ("absolute-URI", "https://datatracker.ietf.org/doc/html/rfc3986#section-4.3"),
966 AuthorityType = Authority,
967 UserinfoEncoderType = Userinfo,
968 RegNameEncoderType = RegName,
969 PathEncoderType = Path,
970 QueryEncoderType = Query,
971 FragmentEncoderType = Fragment,
972}
973
974ri_maybe_ref! {
975 Type = Iri,
976 type_name = "Iri",
977 variable_name = "iri",
978 name = "IRI",
979 indefinite_article = "an",
980 description = "An IRI.",
981 must_be_ascii = false,
982 must_have_scheme = true,
983 rfc = 3987,
984 abnf_rule = ("IRI", "https://datatracker.ietf.org/doc/html/rfc3987#section-2.2"),
985 RefType = IriRef,
986 ref_name = "IRI reference",
987 AuthorityType = IAuthority,
988 UserinfoEncoderType = IUserinfo,
989 RegNameEncoderType = IRegName,
990 PathEncoderType = IPath,
991 QueryEncoderType = IQuery,
992 FragmentEncoderType = IFragment,
993}
994
995ri_maybe_ref! {
996 Type = IriRef,
997 type_name = "IriRef",
998 variable_name = "iri_ref",
999 name = "IRI reference",
1000 indefinite_article = "an",
1001 description = "An IRI reference, i.e., either a IRI or a relative reference.",
1002 must_be_ascii = false,
1003 must_have_scheme = false,
1004 rfc = 3987,
1005 abnf_rule = ("IRI-reference", "https://datatracker.ietf.org/doc/html/rfc3987#section-2.2"),
1006 NonRefType = Iri,
1007 non_ref_name = "IRI",
1008 non_ref_link = "https://datatracker.ietf.org/doc/html/rfc3987#section-2.2",
1009 abnf_rule_absolute = ("absolute-IRI", "https://datatracker.ietf.org/doc/html/rfc3987#section-2.2"),
1010 AuthorityType = IAuthority,
1011 UserinfoEncoderType = IUserinfo,
1012 RegNameEncoderType = IRegName,
1013 PathEncoderType = IPath,
1014 QueryEncoderType = IQuery,
1015 FragmentEncoderType = IFragment,
1016}
1017
1018macro_rules! impl_from {
1019 ($($x:ident => $($y:ident),+)*) => {
1020 $($(
1021 impl<T: Bos<str>> From<$x<T>> for $y<T> {
1022 #[doc = concat!("Consumes the `", stringify!($x), "` and creates a new [`", stringify!($y), "`] with the same contents.")]
1023 fn from(value: $x<T>) -> Self {
1024 RiRef::new(value.val, value.meta)
1025 }
1026 }
1027 )+)*
1028 };
1029}
1030
1031impl_from! {
1032 Uri => UriRef, Iri, IriRef
1033 UriRef => IriRef
1034 Iri => IriRef
1035}
1036
1037macro_rules! impl_try_from {
1038 ($(#[$doc:meta] $x:ident if $($cond:ident)&&+ => $y:ident)*) => {
1039 $(
1040 impl<'a> TryFrom<$x<&'a str>> for $y<&'a str> {
1041 type Error = ParseError;
1042
1043 #[$doc]
1044 fn try_from(value: $x<&'a str>) -> Result<Self, Self::Error> {
1045 let r = value.as_ref();
1046 $(r.$cond()?;)+
1047 Ok((RiRef::new(value.val, value.meta)))
1048 }
1049 }
1050
1051 impl TryFrom<$x<String>> for $y<String> {
1052 type Error = ParseError<$x<String>>;
1053
1054 #[$doc]
1055 fn try_from(value: $x<String>) -> Result<Self, Self::Error> {
1056 let r = value.as_ref();
1057 $(
1058 if let Err(e) = r.$cond() {
1059 return Err(e.with_input(value));
1060 }
1061 )+
1062 Ok((RiRef::new(value.val, value.meta)))
1063 }
1064 }
1065 )*
1066 };
1067}
1068
1069impl_try_from! {
1070 UriRef if ensure_has_scheme => Uri
1072 Iri if ensure_ascii => Uri
1074 IriRef if ensure_has_scheme && ensure_ascii => Uri
1076 IriRef if ensure_ascii => UriRef
1078 IriRef if ensure_has_scheme => Iri
1080}
1081
1082impl<T: Bos<str>> Iri<T> {
1083 pub fn to_uri(&self) -> Uri<String> {
1099 RiRef::from_pair(encode_non_ascii(self.as_ref()))
1100 }
1101}
1102
1103impl<T: Bos<str>> IriRef<T> {
1104 pub fn to_uri_ref(&self) -> UriRef<String> {
1120 RiRef::from_pair(encode_non_ascii(self.as_ref()))
1121 }
1122}
1123
1124fn encode_non_ascii(r: Ref<'_, '_>) -> (String, Meta) {
1125 let mut buf = String::new();
1126 let mut meta = Meta::default();
1127
1128 if let Some(scheme) = r.scheme_opt() {
1129 buf.push_str(scheme.as_str());
1130 meta.scheme_end = NonZeroUsize::new(buf.len());
1131 buf.push(':');
1132 }
1133
1134 if let Some(auth) = r.authority() {
1135 buf.push_str("//");
1136
1137 if let Some(userinfo) = auth.userinfo() {
1138 encode_non_ascii_str(&mut buf, userinfo.as_str());
1139 buf.push('@');
1140 }
1141
1142 let mut auth_meta = auth.meta();
1143 auth_meta.host_bounds.0 = buf.len();
1144 match auth_meta.host_meta {
1145 HostMeta::RegName => encode_non_ascii_str(&mut buf, auth.host()),
1146 _ => buf.push_str(auth.host()),
1147 }
1148 auth_meta.host_bounds.1 = buf.len();
1149 meta.auth_meta = Some(auth_meta);
1150
1151 if let Some(port) = auth.port() {
1152 buf.push(':');
1153 buf.push_str(port.as_str());
1154 }
1155 }
1156
1157 meta.path_bounds.0 = buf.len();
1158 encode_non_ascii_str(&mut buf, r.path().as_str());
1159 meta.path_bounds.1 = buf.len();
1160
1161 if let Some(query) = r.query() {
1162 buf.push('?');
1163 encode_non_ascii_str(&mut buf, query.as_str());
1164 meta.query_end = NonZeroUsize::new(buf.len());
1165 }
1166
1167 if let Some(fragment) = r.fragment() {
1168 buf.push('#');
1169 encode_non_ascii_str(&mut buf, fragment.as_str());
1170 }
1171
1172 (buf, meta)
1173}
1174
1175fn encode_non_ascii_str(buf: &mut String, s: &str) {
1176 if s.is_ascii() {
1177 buf.push_str(s);
1178 } else {
1179 for ch in s.chars() {
1180 if ch.is_ascii() {
1181 buf.push(ch);
1182 } else {
1183 for x in ch.encode_utf8(&mut [0; 4]).bytes() {
1184 encode_byte(x, buf);
1185 }
1186 }
1187 }
1188 }
1189}