fluent_uri/
imp.rs

1#![allow(missing_debug_implementations)]
2
3use crate::{
4    component::{Authority, IAuthority, Scheme},
5    convert::ConvertError,
6    parse::{self, ParseError},
7    pct_enc::{encoder::*, EStr, Encoder},
8};
9use borrow_or_share::{BorrowOrShare, Bos};
10use core::{borrow::Borrow, cmp::Ordering, fmt, hash, num::NonZeroUsize, str};
11
12#[cfg(feature = "alloc")]
13use crate::{
14    build::{
15        state::{NonRefStart, Start},
16        Builder,
17    },
18    normalize::Normalizer,
19    resolve::{self, ResolveError},
20};
21#[cfg(feature = "alloc")]
22use alloc::{borrow::ToOwned, string::String};
23#[cfg(feature = "alloc")]
24use core::str::FromStr;
25
26#[cfg(feature = "net")]
27use crate::net::{Ipv4Addr, Ipv6Addr};
28
29#[cfg(feature = "serde")]
30use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
31
32pub trait Value: Default {}
33
34impl Value for &str {}
35
36#[cfg(feature = "alloc")]
37impl Value for String {}
38
39pub struct Constraints {
40    pub ascii_only: bool,
41    pub scheme_required: bool,
42}
43
44pub trait RiMaybeRef: Sized {
45    type Val;
46    type WithVal<T>: RiMaybeRef<Val = T>;
47
48    type UserinfoE: Encoder;
49    type RegNameE: Encoder;
50    type PathE: Encoder;
51    type QueryE: Encoder;
52    type FragmentE: Encoder;
53
54    const CONSTRAINTS: Constraints;
55
56    fn new(val: Self::Val, meta: Meta) -> Self;
57
58    fn from_pair((val, meta): (Self::Val, Meta)) -> Self {
59        Self::new(val, meta)
60    }
61
62    fn make_ref<'i, 'o>(&'i self) -> RmrRef<'o, 'i>
63    where
64        Self::Val: BorrowOrShare<'i, 'o, str>;
65}
66
67#[cfg(feature = "alloc")]
68pub trait Ri: RiMaybeRef {
69    type Ref<T>: RiMaybeRef<Val = T>;
70}
71
72pub trait Parse {
73    type Val;
74    type Err;
75
76    fn parse<R: RiMaybeRef<Val = Self::Val>>(self) -> Result<R, Self::Err>;
77}
78
79impl<'a> Parse for &'a str {
80    type Val = &'a str;
81    type Err = ParseError;
82
83    fn parse<R: RiMaybeRef<Val = Self::Val>>(self) -> Result<R, Self::Err> {
84        parse::parse(self.as_bytes(), R::CONSTRAINTS).map(|meta| R::new(self, meta))
85    }
86}
87
88#[cfg(feature = "alloc")]
89impl Parse for String {
90    type Val = Self;
91    type Err = (ParseError, Self);
92
93    fn parse<R: RiMaybeRef<Val = Self::Val>>(self) -> Result<R, Self::Err> {
94        match parse::parse(self.as_bytes(), R::CONSTRAINTS) {
95            Ok(meta) => Ok(R::new(self, meta)),
96            Err(e) => Err((e, self)),
97        }
98    }
99}
100
101#[derive(Clone, Copy, Default)]
102pub struct Meta {
103    // The index of the trailing colon.
104    pub scheme_end: Option<NonZeroUsize>,
105    pub auth_meta: Option<AuthMeta>,
106    pub path_bounds: (usize, usize),
107    // One byte past the last byte of query.
108    pub query_end: Option<NonZeroUsize>,
109}
110
111impl Meta {
112    #[inline]
113    pub fn query_or_path_end(&self) -> usize {
114        self.query_end.map_or(self.path_bounds.1, |i| i.get())
115    }
116}
117
118#[derive(Clone, Copy, Default)]
119pub struct AuthMeta {
120    pub host_bounds: (usize, usize),
121    pub host_meta: HostMeta,
122}
123
124impl AuthMeta {
125    pub const EMPTY: Self = Self {
126        host_bounds: (0, 0),
127        host_meta: HostMeta::RegName,
128    };
129}
130
131#[derive(Clone, Copy, Default)]
132pub enum HostMeta {
133    Ipv4(#[cfg(feature = "net")] Ipv4Addr),
134    Ipv6(#[cfg(feature = "net")] Ipv6Addr),
135    IpvFuture,
136    #[default]
137    RegName,
138}
139
140pub trait PathEncoder: Encoder {}
141
142impl PathEncoder for Path {}
143impl PathEncoder for IPath {}
144
145macro_rules! cond {
146    (if true { $($then:tt)* } else { $($else:tt)* }) => { $($then)* };
147    (if false { $($then:tt)* } else { $($else:tt)* }) => { $($else)* };
148}
149
150macro_rules! ri_maybe_ref {
151    (
152        Type = $Ty:ident,
153        type_name = $ty:literal,
154        variable_name = $var:literal,
155        name = $name:literal,
156        indefinite_article = $art:literal,
157        description = $desc:literal,
158        ascii_only = $ascii_only:literal,
159        scheme_required = $scheme_required:tt,
160        rfc = $rfc:literal,
161        abnf_rule = ($abnf:literal, $abnf_link:literal),
162        $(
163            NonRefType = $NonRefTy:ident,
164            non_ref_name = $nr_name:literal,
165            non_ref_link = $nr_link:literal,
166            abnf_rule_absolute = ($abnf_abs:literal, $abnf_abs_link:literal),
167        )?
168        $(
169            RefType = $RefTy:ident,
170            ref_name = $ref_name:literal,
171        )?
172        AuthorityType = $Authority:ident,
173        UserinfoEncoderType = $UserinfoE:ident,
174        RegNameEncoderType = $RegNameE:ident,
175        PathEncoderType = $PathE:ident,
176        QueryEncoderType = $QueryE:ident,
177        FragmentEncoderType = $FragmentE:ident,
178    ) => {
179        #[doc = $desc]
180        ///
181        /// See the [crate-level documentation](crate#terminology) for an explanation of the above term(s).
182        ///
183        /// # Variants
184        ///
185        #[doc = concat!("Two variants of `", $ty, "` are available: ")]
186        #[doc = concat!("`", $ty, "<&str>` (borrowed) and `", $ty, "<String>` (owned).")]
187        ///
188        #[doc = concat!("`", $ty, "<&'a str>`")]
189        /// outputs references with lifetime `'a` where possible
190        /// (thanks to [`borrow-or-share`](borrow_or_share)):
191        ///
192        /// ```
193        #[doc = concat!("use fluent_uri::", $ty, ";")]
194        ///
195        #[doc = concat!("// Keep a reference to the path after dropping the `", $ty, "`.")]
196        #[doc = concat!("let path = ", $ty, "::parse(\"foo:bar\")?.path();")]
197        /// assert_eq!(path, "bar");
198        /// # Ok::<_, fluent_uri::ParseError>(())
199        /// ```
200        ///
201        /// # Comparison
202        ///
203        #[doc = concat!("`", $ty, "`s")]
204        /// are compared [lexicographically](Ord#lexicographical-comparison)
205        /// by their byte values. Normalization is **not** performed prior to comparison.
206        ///
207        /// # Examples
208        ///
209        /// Parse and extract components from
210        #[doc = concat!($art, " ", $name, ":")]
211        ///
212        /// ```
213        /// use fluent_uri::{
214        ///     component::{Host, Scheme},
215        ///     pct_enc::EStr,
216        #[doc = concat!("    ", $ty, ",")]
217        /// };
218        ///
219        /// const SCHEME_FOO: &Scheme = Scheme::new_or_panic("foo");
220        ///
221        /// let s = "foo://user@example.com:8042/over/there?name=ferret#nose";
222        #[doc = concat!("let ", $var, " = ", $ty, "::parse(s)?;")]
223        ///
224        #[doc = concat!("assert_eq!(", $var, ".scheme()",
225            cond!(if $scheme_required { "" } else { ".unwrap()" }), ", SCHEME_FOO);")]
226        ///
227        #[doc = concat!("let auth = ", $var, ".authority().unwrap();")]
228        /// assert_eq!(auth.as_str(), "user@example.com:8042");
229        /// assert_eq!(auth.userinfo().unwrap(), "user");
230        /// assert_eq!(auth.host(), "example.com");
231        /// assert!(matches!(auth.host_parsed(), Host::RegName(name) if name == "example.com"));
232        /// assert_eq!(auth.port().unwrap(), "8042");
233        /// assert_eq!(auth.port_to_u16(), Ok(Some(8042)));
234        ///
235        #[doc = concat!("assert_eq!(", $var, ".path(), \"/over/there\");")]
236        #[doc = concat!("assert_eq!(", $var, ".query().unwrap(), \"name=ferret\");")]
237        #[doc = concat!("assert_eq!(", $var, ".fragment().unwrap(), \"nose\");")]
238        /// # Ok::<_, fluent_uri::ParseError>(())
239        /// ```
240        ///
241        /// Parse into and convert between
242        #[doc = concat!("`", $ty, "<&str>` and `", $ty, "<String>`:")]
243        ///
244        /// ```
245        #[doc = concat!("use fluent_uri::", $ty, ";")]
246        ///
247        /// let s = "http://example.com/";
248        ///
249        #[doc = concat!("// Parse into a `", $ty, "<&str>` from a string slice.")]
250        #[doc = concat!("let ", $var, ": ", $ty, "<&str> = ", $ty, "::parse(s)?;")]
251        ///
252        #[doc = concat!("// Parse into a `", $ty, "<String>` from an owned string.")]
253        #[doc = concat!("let ", $var, "_owned: ", $ty, "<String> = ", $ty, "::parse(s.to_owned()).map_err(|e| e.0)?;")]
254        ///
255        #[doc = concat!("// Convert a `", $ty, "<&str>` to `", $ty, "<String>`.")]
256        #[doc = concat!("let ", $var, "_owned: ", $ty, "<String> = ", $var, ".to_owned();")]
257        ///
258        #[doc = concat!("// Borrow a `", $ty, "<String>` as `", $ty, "<&str>`.")]
259        #[doc = concat!("let ", $var, ": ", $ty, "<&str> = ", $var, "_owned.borrow();")]
260        /// # Ok::<_, fluent_uri::ParseError>(())
261        /// ```
262        #[derive(Clone, Copy)]
263        pub struct $Ty<T> {
264            /// Value of the URI/IRI (reference).
265            pub(crate) val: T,
266            /// Metadata of the URI/IRI (reference).
267            /// Should be identical to parser output with `val` as input.
268            pub(crate) meta: Meta,
269        }
270
271        impl<T> RiMaybeRef for $Ty<T> {
272            type Val = T;
273            type WithVal<U> = $Ty<U>;
274
275            type UserinfoE = $UserinfoE;
276            type RegNameE = $RegNameE;
277            type PathE = $PathE;
278            type QueryE = $QueryE;
279            type FragmentE = $FragmentE;
280
281            const CONSTRAINTS: Constraints = Constraints {
282                ascii_only: $ascii_only,
283                scheme_required: $scheme_required,
284            };
285
286            fn new(val: T, meta: Meta) -> Self {
287                Self { val, meta }
288            }
289
290            fn make_ref<'i, 'o>(&'i self) -> RmrRef<'o, 'i>
291            where
292                Self::Val: BorrowOrShare<'i, 'o, str>,
293            {
294                RmrRef::new(self.as_str(), &self.meta)
295            }
296        }
297
298        $(
299            #[cfg(feature = "alloc")]
300            impl<T: Bos<str>> Ri for $Ty<T> {
301                type Ref<U> = $RefTy<U>;
302            }
303        )?
304
305        impl<T> $Ty<T> {
306            #[doc = concat!("Parses ", $art, " ", $name, " from a string into ", $art, " `", $ty, "`.")]
307            ///
308            /// The return type is
309            ///
310            #[doc = concat!("- `Result<", $ty, "<&str>, ParseError>` for `I = &str`;")]
311            #[doc = concat!("- `Result<", $ty, "<String>, (ParseError, String)>` for `I = String`.")]
312            ///
313            /// # Errors
314            ///
315            /// Returns `Err` if the string does not match the
316            #[doc = concat!("[`", $abnf, "`][abnf] ABNF rule from RFC ", $rfc, ".")]
317            ///
318            #[doc = concat!("[abnf]: ", $abnf_link)]
319            pub fn parse<I>(input: I) -> Result<Self, I::Err>
320            where
321                I: Parse<Val = T>,
322            {
323                input.parse()
324            }
325        }
326
327        #[cfg(feature = "alloc")]
328        impl $Ty<String> {
329            #[doc = concat!("Creates a new builder for ", $name, ".")]
330            #[inline]
331            pub fn builder() -> Builder<Self, cond!(if $scheme_required { NonRefStart } else { Start })> {
332                Builder::new()
333            }
334
335            #[doc = concat!("Borrows this `", $ty, "<String>` as `", $ty, "<&str>`.")]
336            #[allow(clippy::should_implement_trait)]
337            #[inline]
338            #[must_use]
339            pub fn borrow(&self) -> $Ty<&str> {
340                $Ty {
341                    val: &self.val,
342                    meta: self.meta,
343                }
344            }
345
346            #[doc = concat!("Consumes this `", $ty, "<String>` and yields the underlying [`String`].")]
347            #[inline]
348            #[must_use]
349            pub fn into_string(self) -> String {
350                self.val
351            }
352        }
353
354        #[cfg(feature = "alloc")]
355        impl $Ty<&str> {
356            #[doc = concat!("Creates a new `", $ty, "<String>` by cloning the contents of this `", $ty, "<&str>`.")]
357            #[inline]
358            #[must_use]
359            pub fn to_owned(&self) -> $Ty<String> {
360                $Ty {
361                    val: self.val.to_owned(),
362                    meta: self.meta,
363                }
364            }
365        }
366
367        impl<'i, 'o, T: BorrowOrShare<'i, 'o, str>> $Ty<T> {
368            #[doc = concat!("Returns the ", $name, " as a string slice.")]
369            #[must_use]
370            pub fn as_str(&'i self) -> &'o str {
371                self.val.borrow_or_share()
372            }
373
374            cond!(if $scheme_required {
375                /// Returns the [scheme] component.
376                ///
377                /// Note that the scheme component is *case-insensitive*.
378                /// See the documentation of [`Scheme`] for more details on comparison.
379                ///
380                /// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
381                ///
382                /// # Examples
383                ///
384                /// ```
385                #[doc = concat!("use fluent_uri::{component::Scheme, ", $ty, "};")]
386                ///
387                /// const SCHEME_HTTP: &Scheme = Scheme::new_or_panic("http");
388                ///
389                #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
390                #[doc = concat!("assert_eq!(", $var, ".scheme(), SCHEME_HTTP);")]
391                /// # Ok::<_, fluent_uri::ParseError>(())
392                /// ```
393                #[must_use]
394                pub fn scheme(&'i self) -> &'o Scheme {
395                    self.make_ref().scheme()
396                }
397            } else {
398                /// Returns the optional [scheme] component.
399                ///
400                /// Note that the scheme component is *case-insensitive*.
401                /// See the documentation of [`Scheme`] for more details on comparison.
402                ///
403                /// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
404                ///
405                /// # Examples
406                ///
407                /// ```
408                #[doc = concat!("use fluent_uri::{component::Scheme, ", $ty, "};")]
409                ///
410                /// const SCHEME_HTTP: &Scheme = Scheme::new_or_panic("http");
411                ///
412                #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
413                #[doc = concat!("assert_eq!(", $var, ".scheme(), Some(SCHEME_HTTP));")]
414                ///
415                #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"/path/to/file\")?;")]
416                #[doc = concat!("assert_eq!(", $var, ".scheme(), None);")]
417                /// # Ok::<_, fluent_uri::ParseError>(())
418                /// ```
419                #[must_use]
420                pub fn scheme(&'i self) -> Option<&'o Scheme> {
421                    self.make_ref().scheme_opt()
422                }
423            });
424
425            /// Returns the optional [authority] component.
426            ///
427            /// [authority]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2
428            ///
429            /// # Examples
430            ///
431            /// ```
432            #[doc = concat!("use fluent_uri::", $ty, ";")]
433            ///
434            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
435            #[doc = concat!("assert!(", $var, ".authority().is_some());")]
436            ///
437            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"mailto:user@example.com\")?;")]
438            #[doc = concat!("assert!(", $var, ".authority().is_none());")]
439            /// # Ok::<_, fluent_uri::ParseError>(())
440            /// ```
441            #[must_use]
442            pub fn authority(&'i self) -> Option<$Authority<'o>> {
443                self.make_ref().authority().map(Authority::cast)
444            }
445
446            /// Returns the [path] component.
447            ///
448            /// The path component is always present, although it may be empty.
449            ///
450            /// The returned `EStr` slice has [extension methods] for the path component.
451            ///
452            /// [path]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
453            /// [extension methods]: EStr#impl-EStr<E>-1
454            ///
455            /// # Examples
456            ///
457            /// ```
458            #[doc = concat!("use fluent_uri::", $ty, ";")]
459            ///
460            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
461            #[doc = concat!("assert_eq!(", $var, ".path(), \"/\");")]
462            ///
463            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"mailto:user@example.com\")?;")]
464            #[doc = concat!("assert_eq!(", $var, ".path(), \"user@example.com\");")]
465            ///
466            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com\")?;")]
467            #[doc = concat!("assert_eq!(", $var, ".path(), \"\");")]
468            /// # Ok::<_, fluent_uri::ParseError>(())
469            /// ```
470            #[must_use]
471            pub fn path(&'i self) -> &'o EStr<$PathE> {
472                self.make_ref().path().cast()
473            }
474
475            /// Returns the optional [query] component.
476            ///
477            /// [query]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.4
478            ///
479            /// # Examples
480            ///
481            /// ```
482            #[doc = concat!("use fluent_uri::{pct_enc::EStr, ", $ty, "};")]
483            ///
484            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/?lang=en\")?;")]
485            #[doc = concat!("assert_eq!(", $var, ".query(), Some(EStr::new_or_panic(\"lang=en\")));")]
486            ///
487            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"ftp://192.0.2.1/\")?;")]
488            #[doc = concat!("assert_eq!(", $var, ".query(), None);")]
489            /// # Ok::<_, fluent_uri::ParseError>(())
490            /// ```
491            #[must_use]
492            pub fn query(&'i self) -> Option<&'o EStr<$QueryE>> {
493                self.make_ref().query().map(EStr::cast)
494            }
495
496            /// Returns the optional [fragment] component.
497            ///
498            /// [fragment]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.5
499            ///
500            /// # Examples
501            ///
502            /// ```
503            #[doc = concat!("use fluent_uri::{pct_enc::EStr, ", $ty, "};")]
504            ///
505            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/#usage\")?;")]
506            #[doc = concat!("assert_eq!(", $var, ".fragment(), Some(EStr::new_or_panic(\"usage\")));")]
507            ///
508            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"ftp://192.0.2.1/\")?;")]
509            #[doc = concat!("assert_eq!(", $var, ".fragment(), None);")]
510            /// # Ok::<_, fluent_uri::ParseError>(())
511            /// ```
512            #[must_use]
513            pub fn fragment(&'i self) -> Option<&'o EStr<$FragmentE>> {
514                self.make_ref().fragment().map(EStr::cast)
515            }
516        }
517
518        impl<T: Bos<str>> $Ty<T> {
519            $(
520                #[doc = concat!("Resolves the ", $name, " against the given base ", $nr_name)]
521                #[doc = concat!("and returns the target ", $nr_name, ".")]
522                ///
523                #[doc = concat!("The base ", $nr_name)]
524                /// **must** have no fragment, i.e., match the
525                #[doc = concat!("[`", $abnf_abs, "`][abnf] ABNF rule from RFC ", $rfc, ".")]
526                ///
527                #[doc = concat!("To prepare a base ", $nr_name, ",")]
528                /// you can use [`strip_fragment`], [`with_fragment`] or [`set_fragment`] to remove the fragment
529                #[doc = concat!("from any ", $nr_name, ".")]
530                /// Note that a base without fragment does **not** guarantee a successful resolution
531                /// (see the **must** below).
532                ///
533                /// This method applies the reference resolution algorithm defined in
534                /// [Section 5 of RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-5),
535                /// except for the following deviations:
536                ///
537                /// - If `base` has a [rootless] path and no authority, then
538                ///   `self` **must** either have a scheme, be empty, or start with `'#'`.
539                /// - When the target has no authority and its path would start
540                ///   with `"//"`, the string `"/."` is prepended to the path. This closes a
541                ///   loophole in the original algorithm that resolving `".//@@"` against
542                ///   `"foo:/"` yields `"foo://@@"` which is not a valid URI/IRI.
543                /// - Percent-encoded dot segments (e.g. `"%2E"` and `".%2e"`) are also removed.
544                ///   This closes a loophole in the original algorithm that resolving `".."`
545                ///   against `"foo:/bar/baz/.%2E/"` yields `"foo:/bar/baz/"`, while first normalizing
546                ///   the base and then resolving `".."` against it yields `"foo:/"`.
547                /// - A slash (`'/'`) is appended to the base when it ends with a double-dot
548                ///   segment. This closes a loophole in the original algorithm that resolving
549                ///   `"."` against `"foo:/bar/.."` yields `"foo:/bar/"`, while first normalizing
550                ///   the base and then resolving `"."` against it yields `"foo:/"`.
551                /// - When `base` has an [absolute] path and `self` has an empty path and
552                ///   no scheme nor authority, dot segments are removed from the base path before
553                ///   using it as the target path. This closes a loophole in the original algorithm
554                ///   that resolving `""` against `"foo:/."` yields `"foo:/."` in which
555                ///   dot segments are not removed.
556                ///
557                /// No normalization except the removal of dot segments will be performed.
558                /// Use [`normalize`] if necessary.
559                ///
560                #[doc = concat!("[abnf]: ", $abnf_abs_link)]
561                #[doc = concat!("[`strip_fragment`]: ", stringify!($NonRefTy), "::strip_fragment")]
562                #[doc = concat!("[`with_fragment`]: ", stringify!($NonRefTy), "::with_fragment")]
563                #[doc = concat!("[`set_fragment`]: ", stringify!($NonRefTy), "::set_fragment")]
564                /// [rootless]: EStr::<Path>::is_rootless
565                /// [absolute]: EStr::<Path>::is_absolute
566                /// [`normalize`]: Self::normalize
567                ///
568                /// This method has the property that
569                /// `self.resolve_against(base).map(|r| r.normalize()).ok()` equals
570                /// `self.normalize().resolve_against(&base.normalize()).ok()`.
571                ///
572                /// If you need to resolve multiple references against a common base or configure the behavior
573                /// of resolution, consider using [`Resolver`](crate::resolve::Resolver) instead.
574                ///
575                /// # Errors
576                ///
577                /// Returns `Err` if any of the above two **must**s is violated.
578                ///
579                /// # Examples
580                ///
581                /// ```
582                #[doc = concat!("use fluent_uri::{", stringify!($NonRefTy), ", ", $ty, "};")]
583                ///
584                #[doc = concat!("let base = ", stringify!($NonRefTy), "::parse(\"http://example.com/foo/bar\")?;")]
585                ///
586                #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"baz\")?;")]
587                #[doc = concat!("assert_eq!(", $var, ".resolve_against(&base).unwrap(), \"http://example.com/foo/baz\");")]
588                ///
589                #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"../baz\")?;")]
590                #[doc = concat!("assert_eq!(", $var, ".resolve_against(&base).unwrap(), \"http://example.com/baz\");")]
591                ///
592                #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"?baz\")?;")]
593                #[doc = concat!("assert_eq!(", $var, ".resolve_against(&base).unwrap(), \"http://example.com/foo/bar?baz\");")]
594                /// # Ok::<_, fluent_uri::ParseError>(())
595                /// ```
596                #[cfg(feature = "alloc")]
597                pub fn resolve_against<U: Bos<str>>(
598                    &self,
599                    base: &$NonRefTy<U>,
600                ) -> Result<$NonRefTy<String>, ResolveError> {
601                    resolve::resolve(base.make_ref(), self.make_ref(), true).map(RiMaybeRef::from_pair)
602                }
603            )?
604
605            #[doc = concat!("Normalizes the ", $name, ".")]
606            ///
607            /// This method applies syntax-based normalization described in
608            /// [Section 6.2.2 of RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2)
609            /// and [Section 5.3.2 of RFC 3987](https://datatracker.ietf.org/doc/html/rfc3987#section-5.3.2),
610            /// along with IPv6 address and default port normalization.
611            /// This is effectively equivalent to taking the following steps in order:
612            ///
613            /// - Decode any percent-encoded octets that correspond to an allowed character which is not reserved.
614            /// - Uppercase the hexadecimal digits within all percent-encoded octets.
615            /// - Lowercase all ASCII characters within the scheme and the host except the percent-encoded octets.
616            /// - Turn any IPv6 literal address into its canonical form as per
617            ///   [RFC 5952](https://datatracker.ietf.org/doc/html/rfc5952).
618            /// - If the port is empty or equals the [scheme's default], remove it along with the `':'` delimiter.
619            /// - If `self` has a scheme and an [absolute] path, apply the
620            ///   [`remove_dot_segments`] algorithm to the path, taking account of
621            ///   percent-encoded dot segments as described at [`UriRef::resolve_against`].
622            /// - If `self` has no authority and its path would start with
623            ///   `"//"`, prepend `"/."` to the path.
624            ///
625            /// This method is idempotent: `self.normalize()` equals `self.normalize().normalize()`.
626            ///
627            /// If you need to configure the behavior of normalization, consider using [`Normalizer`] instead.
628            ///
629            /// [`UriRef::resolve_against`]: crate::UriRef::resolve_against
630            /// [scheme's default]: Scheme::default_port
631            /// [absolute]: EStr::<Path>::is_absolute
632            /// [`remove_dot_segments`]: https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
633            ///
634            /// # Examples
635            ///
636            /// ```
637            #[doc = concat!("use fluent_uri::", $ty, ";")]
638            ///
639            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"eXAMPLE://a/./b/../b/%63/%7bfoo%7d\")?;")]
640            #[doc = concat!("assert_eq!(", $var, ".normalize(), \"example://a/b/c/%7Bfoo%7D\");")]
641            /// # Ok::<_, fluent_uri::ParseError>(())
642            /// ```
643            #[cfg(feature = "alloc")]
644            #[must_use]
645            pub fn normalize(&self) -> $Ty<String> {
646                Normalizer::new().normalize(self).unwrap()
647            }
648
649            cond!(if $scheme_required {} else {
650                /// Checks whether a scheme component is present.
651                ///
652                /// # Examples
653                ///
654                /// ```
655                #[doc = concat!("use fluent_uri::", $ty, ";")]
656                ///
657                #[doc = concat!("assert!(", $ty, "::parse(\"http://example.com/\")?.has_scheme());")]
658                #[doc = concat!("assert!(!", $ty, "::parse(\"/path/to/file\")?.has_scheme());")]
659                /// # Ok::<_, fluent_uri::ParseError>(())
660                /// ```
661                #[must_use]
662                pub fn has_scheme(&self) -> bool {
663                    self.make_ref().has_scheme()
664                }
665            });
666
667            /// Checks whether an authority component is present.
668            ///
669            /// # Examples
670            ///
671            /// ```
672            #[doc = concat!("use fluent_uri::", $ty, ";")]
673            ///
674            #[doc = concat!("assert!(", $ty, "::parse(\"http://example.com/\")?.has_authority());")]
675            #[doc = concat!("assert!(!", $ty, "::parse(\"mailto:user@example.com\")?.has_authority());")]
676            /// # Ok::<_, fluent_uri::ParseError>(())
677            /// ```
678            #[must_use]
679            pub fn has_authority(&self) -> bool {
680                self.make_ref().has_authority()
681            }
682
683            /// Checks whether a query component is present.
684            ///
685            /// # Examples
686            ///
687            /// ```
688            #[doc = concat!("use fluent_uri::", $ty, ";")]
689            ///
690            #[doc = concat!("assert!(", $ty, "::parse(\"http://example.com/?lang=en\")?.has_query());")]
691            #[doc = concat!("assert!(!", $ty, "::parse(\"ftp://192.0.2.1/\")?.has_query());")]
692            /// # Ok::<_, fluent_uri::ParseError>(())
693            /// ```
694            #[must_use]
695            pub fn has_query(&self) -> bool {
696                self.make_ref().has_query()
697            }
698
699            /// Checks whether a fragment component is present.
700            ///
701            /// # Examples
702            ///
703            /// ```
704            #[doc = concat!("use fluent_uri::", $ty, ";")]
705            ///
706            #[doc = concat!("assert!(", $ty, "::parse(\"http://example.com/#usage\")?.has_fragment());")]
707            #[doc = concat!("assert!(!", $ty, "::parse(\"ftp://192.0.2.1/\")?.has_fragment());")]
708            /// # Ok::<_, fluent_uri::ParseError>(())
709            /// ```
710            #[must_use]
711            pub fn has_fragment(&self) -> bool {
712                self.make_ref().has_fragment()
713            }
714
715            #[doc = concat!("Returns a slice of this ", $name)]
716            /// with the fragment component removed.
717            ///
718            /// # Examples
719            ///
720            /// ```
721            #[doc = concat!("use fluent_uri::", $ty, ";")]
722            ///
723            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/#fragment\")?;")]
724            #[doc = concat!("assert_eq!(", $var, ".strip_fragment(), \"http://example.com/\");")]
725            /// # Ok::<_, fluent_uri::ParseError>(())
726            /// ```
727            #[must_use]
728            pub fn strip_fragment(&self) -> $Ty<&str> {
729                // Altering only the fragment does not change the metadata.
730                RiMaybeRef::new(self.make_ref().strip_fragment(), self.meta)
731            }
732
733            #[doc = concat!("Creates a new ", $name)]
734            /// by replacing the fragment component of `self` with the given one.
735            ///
736            /// The fragment component is removed when `opt.is_none()`.
737            ///
738            /// # Examples
739            ///
740            /// ```
741            #[doc = concat!("use fluent_uri::{pct_enc::EStr, ", $ty, "};")]
742            ///
743            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
744            /// assert_eq!(
745            #[doc = concat!("    ", $var, ".with_fragment(Some(EStr::new_or_panic(\"fragment\"))),")]
746            ///     "http://example.com/#fragment"
747            /// );
748            ///
749            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/#fragment\")?;")]
750            #[doc = concat!("assert_eq!(", $var, ".with_fragment(None), \"http://example.com/\");")]
751            /// # Ok::<_, fluent_uri::ParseError>(())
752            /// ```
753            #[cfg(feature = "alloc")]
754            #[must_use]
755            pub fn with_fragment(&self, opt: Option<&EStr<$FragmentE>>) -> $Ty<String> {
756                // Altering only the fragment does not change the metadata.
757                RiMaybeRef::new(self.make_ref().with_fragment(opt.map(EStr::as_str)), self.meta)
758            }
759        }
760
761        #[cfg(feature = "alloc")]
762        impl $Ty<String> {
763            /// Replaces the fragment component of `self` with the given one.
764            ///
765            /// The fragment component is removed when `opt.is_none()`.
766            ///
767            /// # Examples
768            ///
769            /// ```
770            #[doc = concat!("use fluent_uri::{pct_enc::EStr, ", $ty, "};")]
771            ///
772            #[doc = concat!("let mut ", $var, " = ", $ty, "::parse(\"http://example.com/\")?.to_owned();")]
773            ///
774            #[doc = concat!($var, ".set_fragment(Some(EStr::new_or_panic(\"fragment\")));")]
775            #[doc = concat!("assert_eq!(", $var, ", \"http://example.com/#fragment\");")]
776            ///
777            #[doc = concat!($var, ".set_fragment(None);")]
778            #[doc = concat!("assert_eq!(", $var, ", \"http://example.com/\");")]
779            /// # Ok::<_, fluent_uri::ParseError>(())
780            /// ```
781            pub fn set_fragment(&mut self, opt: Option<&EStr<$FragmentE>>) {
782                // Altering only the fragment does not change the metadata.
783                RmrRef::set_fragment(&mut self.val, &self.meta, opt.map(EStr::as_str))
784            }
785        }
786
787        impl<T: Value> Default for $Ty<T> {
788            #[doc = concat!("Creates an empty ", $name, ".")]
789            fn default() -> Self {
790                Self {
791                    val: T::default(),
792                    meta: Meta::default(),
793                }
794            }
795        }
796
797        impl<T: Bos<str>, U: Bos<str>> PartialEq<$Ty<U>> for $Ty<T> {
798            fn eq(&self, other: &$Ty<U>) -> bool {
799                self.as_str() == other.as_str()
800            }
801        }
802
803        impl<T: Bos<str>> PartialEq<str> for $Ty<T> {
804            fn eq(&self, other: &str) -> bool {
805                self.as_str() == other
806            }
807        }
808
809        impl<T: Bos<str>> PartialEq<$Ty<T>> for str {
810            fn eq(&self, other: &$Ty<T>) -> bool {
811                self == other.as_str()
812            }
813        }
814
815        impl<T: Bos<str>> PartialEq<&str> for $Ty<T> {
816            fn eq(&self, other: &&str) -> bool {
817                self.as_str() == *other
818            }
819        }
820
821        impl<T: Bos<str>> PartialEq<$Ty<T>> for &str {
822            fn eq(&self, other: &$Ty<T>) -> bool {
823                *self == other.as_str()
824            }
825        }
826
827        impl<T: Bos<str>> Eq for $Ty<T> {}
828
829        impl<T: Bos<str>> hash::Hash for $Ty<T> {
830            fn hash<H: hash::Hasher>(&self, state: &mut H) {
831                self.as_str().hash(state);
832            }
833        }
834
835        impl<T: Bos<str>> PartialOrd for $Ty<T> {
836            fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
837                Some(self.cmp(other))
838            }
839        }
840
841        impl<T: Bos<str>> Ord for $Ty<T> {
842            fn cmp(&self, other: &Self) -> Ordering {
843                self.as_str().cmp(other.as_str())
844            }
845        }
846
847        impl<T: Bos<str>> AsRef<str> for $Ty<T> {
848            fn as_ref(&self) -> &str {
849                self.as_str()
850            }
851        }
852
853        impl<T: Bos<str>> Borrow<str> for $Ty<T> {
854            fn borrow(&self) -> &str {
855                self.as_str()
856            }
857        }
858
859        impl<'a> TryFrom<&'a str> for $Ty<&'a str> {
860            type Error = ParseError;
861
862            /// Equivalent to [`parse`](Self::parse).
863            #[inline]
864            fn try_from(value: &'a str) -> Result<Self, Self::Error> {
865                $Ty::parse(value)
866            }
867        }
868
869        #[cfg(feature = "alloc")]
870        impl TryFrom<String> for $Ty<String> {
871            type Error = (ParseError, String);
872
873            /// Equivalent to [`parse`](Self::parse).
874            #[inline]
875            fn try_from(value: String) -> Result<Self, Self::Error> {
876                $Ty::parse(value)
877            }
878        }
879
880        impl<'a> From<$Ty<&'a str>> for &'a str {
881            #[doc = concat!("Equivalent to [`as_str`](", $ty, "::as_str).")]
882            #[inline]
883            fn from(value: $Ty<&'a str>) -> &'a str {
884                value.val
885            }
886        }
887
888        #[cfg(feature = "alloc")]
889        impl<'a> From<$Ty<String>> for String {
890            #[doc = concat!("Equivalent to [`into_string`](", $ty, "::into_string).")]
891            #[inline]
892            fn from(value: $Ty<String>) -> String {
893                value.val
894            }
895        }
896
897        #[cfg(feature = "alloc")]
898        impl From<$Ty<&str>> for $Ty<String> {
899            /// Equivalent to [`to_owned`](Self::to_owned).
900            #[inline]
901            fn from(value: $Ty<&str>) -> Self {
902                value.to_owned()
903            }
904        }
905
906        #[cfg(feature = "alloc")]
907        impl FromStr for $Ty<String> {
908            type Err = ParseError;
909
910            #[doc = concat!("Equivalent to `", $ty, "::parse(s).map(|r| r.to_owned())`.")]
911            #[inline]
912            fn from_str(s: &str) -> Result<Self, Self::Err> {
913                $Ty::parse(s).map(|r| r.to_owned())
914            }
915        }
916
917        impl<T: Bos<str>> fmt::Debug for $Ty<T> {
918            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
919                f.debug_struct($ty)
920                    .field("scheme", &self.scheme())
921                    .field("authority", &self.authority())
922                    .field("path", &self.path())
923                    .field("query", &self.query())
924                    .field("fragment", &self.fragment())
925                    .finish()
926            }
927        }
928
929        impl<T: Bos<str>> fmt::Display for $Ty<T> {
930            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
931                fmt::Display::fmt(self.as_str(), f)
932            }
933        }
934
935        #[cfg(feature = "serde")]
936        impl<T: Bos<str>> Serialize for $Ty<T> {
937            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
938            where
939                S: Serializer,
940            {
941                serializer.serialize_str(self.as_str())
942            }
943        }
944
945        #[cfg(feature = "serde")]
946        impl<'de> Deserialize<'de> for $Ty<&'de str> {
947            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
948            where
949                D: Deserializer<'de>,
950            {
951                let s = <&str>::deserialize(deserializer)?;
952                $Ty::parse(s).map_err(|e| {
953                    de::Error::custom(format_args!(
954                        "failed to parse {s:?} as {}: {e}",
955                        $name
956                    ))
957                })
958            }
959        }
960
961        #[cfg(feature = "serde")]
962        impl<'de> Deserialize<'de> for $Ty<String> {
963            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
964            where
965                D: Deserializer<'de>,
966            {
967                let s = String::deserialize(deserializer)?;
968                $Ty::parse(s).map_err(|(s, e)| {
969                    de::Error::custom(format_args!(
970                        "failed to parse {s:?} as {}: {e}",
971                        $name
972                    ))
973                })
974            }
975        }
976    };
977}
978
979/// References to the value and the metadata of an `RiMaybeRef`.
980#[derive(Clone, Copy)]
981pub struct RmrRef<'v, 'm> {
982    val: &'v str,
983    meta: &'m Meta,
984}
985
986impl<'v, 'm> RmrRef<'v, 'm> {
987    pub fn new(val: &'v str, meta: &'m Meta) -> Self {
988        Self { val, meta }
989    }
990
991    pub fn as_str(self) -> &'v str {
992        self.val
993    }
994
995    fn slice(self, start: usize, end: usize) -> &'v str {
996        &self.val[start..end]
997    }
998
999    fn eslice<E: Encoder>(self, start: usize, end: usize) -> &'v EStr<E> {
1000        EStr::new_validated(self.slice(start, end))
1001    }
1002
1003    pub fn scheme_opt(self) -> Option<&'v Scheme> {
1004        let end = self.meta.scheme_end?.get();
1005        Some(Scheme::new_validated(self.slice(0, end)))
1006    }
1007
1008    pub fn scheme(self) -> &'v Scheme {
1009        let end = self.meta.scheme_end.map_or(0, |i| i.get());
1010        Scheme::new_validated(self.slice(0, end))
1011    }
1012
1013    pub fn authority(self) -> Option<IAuthority<'v>> {
1014        let mut meta = self.meta.auth_meta?;
1015        let start = match self.meta.scheme_end {
1016            Some(i) => i.get() + 3,
1017            None => 2,
1018        };
1019        let end = self.meta.path_bounds.0;
1020
1021        meta.host_bounds.0 -= start;
1022        meta.host_bounds.1 -= start;
1023
1024        Some(IAuthority::new(self.slice(start, end), meta))
1025    }
1026
1027    pub fn path(self) -> &'v EStr<IPath> {
1028        self.eslice(self.meta.path_bounds.0, self.meta.path_bounds.1)
1029    }
1030
1031    pub fn query(self) -> Option<&'v EStr<IQuery>> {
1032        let end = self.meta.query_end?.get();
1033        Some(self.eslice(self.meta.path_bounds.1 + 1, end))
1034    }
1035
1036    fn fragment_start(self) -> Option<usize> {
1037        Some(self.meta.query_or_path_end())
1038            .filter(|&i| i != self.val.len())
1039            .map(|i| i + 1)
1040    }
1041
1042    pub fn fragment(self) -> Option<&'v EStr<IFragment>> {
1043        self.fragment_start()
1044            .map(|i| self.eslice(i, self.val.len()))
1045    }
1046
1047    #[cfg(feature = "alloc")]
1048    pub fn set_fragment(buf: &mut String, meta: &Meta, opt: Option<&str>) {
1049        buf.truncate(meta.query_or_path_end());
1050        if let Some(s) = opt {
1051            buf.reserve_exact(s.len() + 1);
1052            buf.push('#');
1053            buf.push_str(s);
1054        }
1055    }
1056
1057    pub fn strip_fragment(self) -> &'v str {
1058        &self.val[..self.meta.query_or_path_end()]
1059    }
1060
1061    #[cfg(feature = "alloc")]
1062    pub fn with_fragment(self, opt: Option<&str>) -> String {
1063        let stripped = self.strip_fragment();
1064        if let Some(s) = opt {
1065            [stripped, "#", s].concat()
1066        } else {
1067            stripped.to_owned()
1068        }
1069    }
1070
1071    #[inline]
1072    pub fn has_scheme(self) -> bool {
1073        self.meta.scheme_end.is_some()
1074    }
1075
1076    #[inline]
1077    pub fn has_authority(self) -> bool {
1078        self.meta.auth_meta.is_some()
1079    }
1080
1081    #[inline]
1082    pub fn has_query(self) -> bool {
1083        self.meta.query_end.is_some()
1084    }
1085
1086    #[inline]
1087    pub fn has_fragment(self) -> bool {
1088        self.meta.query_or_path_end() != self.val.len()
1089    }
1090
1091    pub fn ensure_has_scheme(self) -> Result<(), ConvertError> {
1092        if self.has_scheme() {
1093            Ok(())
1094        } else {
1095            Err(ConvertError::NoScheme)
1096        }
1097    }
1098
1099    pub fn ensure_ascii(self) -> Result<(), ConvertError> {
1100        match self.as_str().bytes().position(|x| !x.is_ascii()) {
1101            Some(index) => Err(ConvertError::NotAscii { index }),
1102            None => Ok(()),
1103        }
1104    }
1105}
1106
1107ri_maybe_ref! {
1108    Type = Uri,
1109    type_name = "Uri",
1110    variable_name = "uri",
1111    name = "URI",
1112    indefinite_article = "a",
1113    description = "A URI.",
1114    ascii_only = true,
1115    scheme_required = true,
1116    rfc = 3986,
1117    abnf_rule = ("URI", "https://datatracker.ietf.org/doc/html/rfc3986#section-3"),
1118    RefType = UriRef,
1119    ref_name = "URI reference",
1120    AuthorityType = Authority,
1121    UserinfoEncoderType = Userinfo,
1122    RegNameEncoderType = RegName,
1123    PathEncoderType = Path,
1124    QueryEncoderType = Query,
1125    FragmentEncoderType = Fragment,
1126}
1127
1128ri_maybe_ref! {
1129    Type = UriRef,
1130    type_name = "UriRef",
1131    variable_name = "uri_ref",
1132    name = "URI reference",
1133    indefinite_article = "a",
1134    description = "A URI reference, i.e., either a URI or a relative reference.",
1135    ascii_only = true,
1136    scheme_required = false,
1137    rfc = 3986,
1138    abnf_rule = ("URI-reference", "https://datatracker.ietf.org/doc/html/rfc3986#section-4.1"),
1139    NonRefType = Uri,
1140    non_ref_name = "URI",
1141    non_ref_link = "https://datatracker.ietf.org/doc/html/rfc3986#section-3",
1142    abnf_rule_absolute = ("absolute-URI", "https://datatracker.ietf.org/doc/html/rfc3986#section-4.3"),
1143    AuthorityType = Authority,
1144    UserinfoEncoderType = Userinfo,
1145    RegNameEncoderType = RegName,
1146    PathEncoderType = Path,
1147    QueryEncoderType = Query,
1148    FragmentEncoderType = Fragment,
1149}
1150
1151ri_maybe_ref! {
1152    Type = Iri,
1153    type_name = "Iri",
1154    variable_name = "iri",
1155    name = "IRI",
1156    indefinite_article = "an",
1157    description = "An IRI.",
1158    ascii_only = false,
1159    scheme_required = true,
1160    rfc = 3987,
1161    abnf_rule = ("IRI", "https://datatracker.ietf.org/doc/html/rfc3987#section-2.2"),
1162    RefType = IriRef,
1163    ref_name = "IRI reference",
1164    AuthorityType = IAuthority,
1165    UserinfoEncoderType = IUserinfo,
1166    RegNameEncoderType = IRegName,
1167    PathEncoderType = IPath,
1168    QueryEncoderType = IQuery,
1169    FragmentEncoderType = IFragment,
1170}
1171
1172ri_maybe_ref! {
1173    Type = IriRef,
1174    type_name = "IriRef",
1175    variable_name = "iri_ref",
1176    name = "IRI reference",
1177    indefinite_article = "an",
1178    description = "An IRI reference, i.e., either a IRI or a relative reference.",
1179    ascii_only = false,
1180    scheme_required = false,
1181    rfc = 3987,
1182    abnf_rule = ("IRI-reference", "https://datatracker.ietf.org/doc/html/rfc3987#section-2.2"),
1183    NonRefType = Iri,
1184    non_ref_name = "IRI",
1185    non_ref_link = "https://datatracker.ietf.org/doc/html/rfc3987#section-2.2",
1186    abnf_rule_absolute = ("absolute-IRI", "https://datatracker.ietf.org/doc/html/rfc3987#section-2.2"),
1187    AuthorityType = IAuthority,
1188    UserinfoEncoderType = IUserinfo,
1189    RegNameEncoderType = IRegName,
1190    PathEncoderType = IPath,
1191    QueryEncoderType = IQuery,
1192    FragmentEncoderType = IFragment,
1193}