fluent_uri/
ri.rs

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        ///
61        /// See the [crate-level documentation](crate#terminology) for an explanation of the above term(s).
62        ///
63        /// # Variants
64        ///
65        #[doc = concat!("Two variants of `", $ty, "` are available: ")]
66        #[doc = concat!("`", $ty, "<&str>` (borrowed) and `", $ty, "<String>` (owned).")]
67        ///
68        #[doc = concat!("`", $ty, "<&'a str>`")]
69        /// outputs references with lifetime `'a` where possible
70        /// (thanks to [`borrow-or-share`](borrow_or_share)):
71        ///
72        /// ```
73        #[doc = concat!("use fluent_uri::", $ty, ";")]
74        ///
75        #[doc = concat!("// Keep a reference to the path after dropping the `", $ty, "`.")]
76        #[doc = concat!("let path = ", $ty, "::parse(\"foo:bar\")?.path();")]
77        /// assert_eq!(path, "bar");
78        /// # Ok::<_, fluent_uri::error::ParseError>(())
79        /// ```
80        ///
81        /// # Comparison
82        ///
83        #[doc = concat!("`", $ty, "`s")]
84        /// are compared [lexicographically](Ord#lexicographical-comparison)
85        /// by their byte values. Normalization is **not** performed prior to comparison.
86        ///
87        /// # Examples
88        ///
89        /// Parse and extract components from
90        #[doc = concat!($art, " ", $name, ":")]
91        ///
92        /// ```
93        /// use fluent_uri::{
94        ///     component::{Host, Scheme},
95        ///     encoding::EStr,
96        #[doc = concat!("    ", $ty, ",")]
97        /// };
98        ///
99        /// const SCHEME_FOO: &Scheme = Scheme::new_or_panic("foo");
100        ///
101        /// let s = "foo://user@example.com:8042/over/there?name=ferret#nose";
102        #[doc = concat!("let ", $var, " = ", $ty, "::parse(s)?;")]
103        ///
104        #[doc = concat!("assert_eq!(", $var, ".scheme()",
105            cond!(if $must_have_scheme { "" } else { ".unwrap()" }), ", SCHEME_FOO);")]
106        ///
107        #[doc = concat!("let auth = ", $var, ".authority().unwrap();")]
108        /// assert_eq!(auth.as_str(), "user@example.com:8042");
109        /// assert_eq!(auth.userinfo().unwrap(), "user");
110        /// assert_eq!(auth.host(), "example.com");
111        /// assert!(matches!(auth.host_parsed(), Host::RegName(name) if name == "example.com"));
112        /// assert_eq!(auth.port().unwrap(), "8042");
113        /// assert_eq!(auth.port_to_u16(), Ok(Some(8042)));
114        ///
115        #[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        /// # Ok::<_, fluent_uri::error::ParseError>(())
119        /// ```
120        ///
121        /// Parse into and convert between
122        #[doc = concat!("`", $ty, "<&str>` and `", $ty, "<String>`:")]
123        ///
124        /// ```
125        #[doc = concat!("use fluent_uri::", $ty, ";")]
126        ///
127        /// let s = "http://example.com/";
128        ///
129        #[doc = concat!("// Parse into a `", $ty, "<&str>` from a string slice.")]
130        #[doc = concat!("let ", $var, ": ", $ty, "<&str> = ", $ty, "::parse(s)?;")]
131        ///
132        #[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        ///
135        #[doc = concat!("// Convert a `", $ty, "<&str>` to `", $ty, "<String>`.")]
136        #[doc = concat!("let ", $var, "_owned: ", $ty, "<String> = ", $var, ".to_owned();")]
137        ///
138        #[doc = concat!("// Borrow a `", $ty, "<String>` as `", $ty, "<&str>`.")]
139        #[doc = concat!("let ", $var, ": ", $ty, "<&str> = ", $var, "_owned.borrow();")]
140        /// # Ok::<_, fluent_uri::error::ParseError>(())
141        /// ```
142        #[derive(Clone, Copy)]
143        pub struct $Ty<T> {
144            /// Value of the URI/IRI (reference).
145            val: T,
146            /// Metadata of the URI/IRI (reference).
147            /// Should be identical to parser output with `val` as input.
148            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            ///
174            /// The return type is
175            ///
176            #[doc = concat!("- `Result<", $ty, "<&str>, ParseError>` for `I = &str`;")]
177            #[doc = concat!("- `Result<", $ty, "<String>, ParseError<String>>` for `I = String`.")]
178            ///
179            /// # Errors
180            ///
181            /// Returns `Err` if the string does not match the
182            #[doc = concat!("[`", $abnf, "`][abnf] ABNF rule from RFC ", $rfc, ".")]
183            ///
184            /// From a [`ParseError<String>`], you may recover or strip the input
185            /// by calling [`into_input`] or [`strip_input`] on it.
186            ///
187            #[doc = concat!("[abnf]: ", $abnf_link)]
188            /// [`into_input`]: ParseError::into_input
189            /// [`strip_input`]: ParseError::strip_input
190            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                /// Returns the [scheme] component.
249                ///
250                /// Note that the scheme component is *case-insensitive*.
251                /// See the documentation of [`Scheme`] for more details on comparison.
252                ///
253                /// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
254                ///
255                /// # Examples
256                ///
257                /// ```
258                #[doc = concat!("use fluent_uri::{component::Scheme, ", $ty, "};")]
259                ///
260                /// const SCHEME_HTTP: &Scheme = Scheme::new_or_panic("http");
261                ///
262                #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
263                #[doc = concat!("assert_eq!(", $var, ".scheme(), SCHEME_HTTP);")]
264                /// # Ok::<_, fluent_uri::error::ParseError>(())
265                /// ```
266                #[must_use]
267                pub fn scheme(&'i self) -> &'o Scheme {
268                    self.as_ref().scheme()
269                }
270            } else {
271                /// Returns the optional [scheme] component.
272                ///
273                /// Note that the scheme component is *case-insensitive*.
274                /// See the documentation of [`Scheme`] for more details on comparison.
275                ///
276                /// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
277                ///
278                /// # Examples
279                ///
280                /// ```
281                #[doc = concat!("use fluent_uri::{component::Scheme, ", $ty, "};")]
282                ///
283                /// const SCHEME_HTTP: &Scheme = Scheme::new_or_panic("http");
284                ///
285                #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
286                #[doc = concat!("assert_eq!(", $var, ".scheme(), Some(SCHEME_HTTP));")]
287                ///
288                #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"/path/to/file\")?;")]
289                #[doc = concat!("assert_eq!(", $var, ".scheme(), None);")]
290                /// # Ok::<_, fluent_uri::error::ParseError>(())
291                /// ```
292                #[must_use]
293                pub fn scheme(&'i self) -> Option<&'o Scheme> {
294                    self.as_ref().scheme_opt()
295                }
296            });
297
298            /// Returns the optional [authority] component.
299            ///
300            /// [authority]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2
301            ///
302            /// # Examples
303            ///
304            /// ```
305            #[doc = concat!("use fluent_uri::", $ty, ";")]
306            ///
307            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
308            #[doc = concat!("assert!(", $var, ".authority().is_some());")]
309            ///
310            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"mailto:user@example.com\")?;")]
311            #[doc = concat!("assert!(", $var, ".authority().is_none());")]
312            /// # Ok::<_, fluent_uri::error::ParseError>(())
313            /// ```
314            #[must_use]
315            pub fn authority(&'i self) -> Option<$Authority<'o>> {
316                self.as_ref().authority().map(Authority::cast)
317            }
318
319            /// Returns the [path] component.
320            ///
321            /// The path component is always present, although it may be empty.
322            ///
323            /// The returned `EStr` slice has [extension methods] for the path component.
324            ///
325            /// [path]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
326            /// [extension methods]: EStr#impl-EStr<E>-1
327            ///
328            /// # Examples
329            ///
330            /// ```
331            #[doc = concat!("use fluent_uri::", $ty, ";")]
332            ///
333            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
334            #[doc = concat!("assert_eq!(", $var, ".path(), \"/\");")]
335            ///
336            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"mailto:user@example.com\")?;")]
337            #[doc = concat!("assert_eq!(", $var, ".path(), \"user@example.com\");")]
338            ///
339            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com\")?;")]
340            #[doc = concat!("assert_eq!(", $var, ".path(), \"\");")]
341            /// # Ok::<_, fluent_uri::error::ParseError>(())
342            /// ```
343            #[must_use]
344            pub fn path(&'i self) -> &'o EStr<$PathE> {
345                self.as_ref().path().cast()
346            }
347
348            /// Returns the optional [query] component.
349            ///
350            /// [query]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.4
351            ///
352            /// # Examples
353            ///
354            /// ```
355            #[doc = concat!("use fluent_uri::{encoding::EStr, ", $ty, "};")]
356            ///
357            #[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            ///
360            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"ftp://192.0.2.1/\")?;")]
361            #[doc = concat!("assert_eq!(", $var, ".query(), None);")]
362            /// # Ok::<_, fluent_uri::error::ParseError>(())
363            /// ```
364            #[must_use]
365            pub fn query(&'i self) -> Option<&'o EStr<$QueryE>> {
366                self.as_ref().query().map(EStr::cast)
367            }
368
369            /// Returns the optional [fragment] component.
370            ///
371            /// [fragment]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.5
372            ///
373            /// # Examples
374            ///
375            /// ```
376            #[doc = concat!("use fluent_uri::{encoding::EStr, ", $ty, "};")]
377            ///
378            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/#usage\")?;")]
379            #[doc = concat!("assert_eq!(", $var, ".fragment(), Some(EStr::new_or_panic(\"usage\")));")]
380            ///
381            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"ftp://192.0.2.1/\")?;")]
382            #[doc = concat!("assert_eq!(", $var, ".fragment(), None);")]
383            /// # Ok::<_, fluent_uri::error::ParseError>(())
384            /// ```
385            #[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                ///
396                #[doc = concat!("The base ", $nr_name)]
397                /// **must** contain no fragment, i.e., match the
398                #[doc = concat!("[`", $abnf_abs, "`][abnf] ABNF rule from RFC ", $rfc, ".")]
399                ///
400                #[doc = concat!("To prepare a base ", $nr_name, ",")]
401                /// you can use [`with_fragment`] or [`set_fragment`] to remove the fragment
402                #[doc = concat!("from any ", $nr_name, ".")]
403                /// Note that a base without fragment does **not** guarantee a successful resolution
404                /// (see the **must** below).
405                ///
406                /// This method applies the reference resolution algorithm defined in
407                /// [Section 5 of RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-5),
408                /// except for the following deviations:
409                ///
410                /// - If `base` contains no authority and its path is [rootless], then
411                ///   `self` **must** either contain a scheme, be empty, or start with `'#'`.
412                /// - When the target contains no authority and its path would start
413                ///   with `"//"`, the string `"/."` is prepended to the path. This closes a
414                ///   loophole in the original algorithm that resolving `".//@@"` against
415                ///   `"foo:/"` yields `"foo://@@"` which is not a valid URI/IRI.
416                /// - Percent-encoded dot segments (e.g. `"%2E"` and `".%2e"`) are also removed.
417                ///   This closes a loophole in the original algorithm that resolving `".."`
418                ///   against `"foo:/bar/.%2E/"` yields `"foo:/bar/"`, while first normalizing
419                ///   the base and then resolving `".."` against it yields `"foo:/"`.
420                /// - A slash (`'/'`) is appended to the base when it ends with a double-dot
421                ///   segment. This closes a loophole in the original algorithm that resolving
422                ///   `"."` against `"foo:/bar/.."` yields `"foo:/bar/"`, while first normalizing
423                ///   the base and then resolving `"."` against it yields `"foo:/"`.
424                ///
425                /// No normalization except the removal of dot segments will be performed.
426                /// Use [`normalize`] if necessary.
427                ///
428                #[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                /// [rootless]: EStr::<Path>::is_rootless
432                /// [`normalize`]: Self::normalize
433                ///
434                /// This method has the property that
435                /// `self.resolve_against(base).map(|r| r.normalize()).ok()` equals
436                /// `self.normalize().resolve_against(&base.normalize()).ok()`.
437                ///
438                /// # Errors
439                ///
440                /// Returns `Err` if any of the above two **must**s is violated.
441                ///
442                /// # Examples
443                ///
444                /// ```
445                #[doc = concat!("use fluent_uri::{", stringify!($NonRefTy), ", ", $ty, "};")]
446                ///
447                #[doc = concat!("let base = ", stringify!($NonRefTy), "::parse(\"http://example.com/foo/bar\")?;")]
448                ///
449                #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"baz\")?;")]
450                #[doc = concat!("assert_eq!(", $var, ".resolve_against(&base).unwrap(), \"http://example.com/foo/baz\");")]
451                ///
452                #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"../baz\")?;")]
453                #[doc = concat!("assert_eq!(", $var, ".resolve_against(&base).unwrap(), \"http://example.com/baz\");")]
454                ///
455                #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"?baz\")?;")]
456                #[doc = concat!("assert_eq!(", $var, ".resolve_against(&base).unwrap(), \"http://example.com/foo/bar?baz\");")]
457                /// # Ok::<_, fluent_uri::error::ParseError>(())
458                /// ```
459                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            ///
469            /// This method applies the syntax-based normalization described in
470            /// [Section 6.2.2 of RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2)
471            /// and [Section 5.3.2 of RFC 3987](https://datatracker.ietf.org/doc/html/rfc3987#section-5.3.2),
472            /// which is effectively equivalent to taking the following steps in order:
473            ///
474            /// - Decode any percent-encoded octets that correspond to an allowed character which is not reserved.
475            /// - Uppercase the hexadecimal digits within all percent-encoded octets.
476            /// - Lowercase all ASCII characters within the scheme and the host except the percent-encoded octets.
477            /// - Turn any IPv6 literal address into its canonical form as per
478            ///   [RFC 5952](https://datatracker.ietf.org/doc/html/rfc5952).
479            /// - If the port is empty, remove its `':'` delimiter.
480            /// - If `self` contains a scheme and an absolute path, apply the
481            ///   [`remove_dot_segments`] algorithm to the path, taking account of
482            ///   percent-encoded dot segments as described at [`UriRef::resolve_against`].
483            /// - If `self` contains no authority and its path would start with
484            ///   `"//"`, prepend `"/."` to the path.
485            ///
486            /// This method is idempotent: `self.normalize()` equals `self.normalize().normalize()`.
487            ///
488            /// [`UriRef::resolve_against`]: crate::UriRef::resolve_against
489            /// [`remove_dot_segments`]: https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
490            ///
491            /// # Examples
492            ///
493            /// ```
494            #[doc = concat!("use fluent_uri::", $ty, ";")]
495            ///
496            #[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            /// # Ok::<_, fluent_uri::error::ParseError>(())
499            /// ```
500            #[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                /// Checks whether a scheme component is present.
507                ///
508                /// # Examples
509                ///
510                /// ```
511                #[doc = concat!("use fluent_uri::", $ty, ";")]
512                ///
513                #[doc = concat!("assert!(", $ty, "::parse(\"http://example.com/\")?.has_scheme());")]
514                #[doc = concat!("assert!(!", $ty, "::parse(\"/path/to/file\")?.has_scheme());")]
515                /// # Ok::<_, fluent_uri::error::ParseError>(())
516                /// ```
517                #[must_use]
518                pub fn has_scheme(&self) -> bool {
519                    self.as_ref().has_scheme()
520                }
521            });
522
523            /// Checks whether an authority component is present.
524            ///
525            /// # Examples
526            ///
527            /// ```
528            #[doc = concat!("use fluent_uri::", $ty, ";")]
529            ///
530            #[doc = concat!("assert!(", $ty, "::parse(\"http://example.com/\")?.has_authority());")]
531            #[doc = concat!("assert!(!", $ty, "::parse(\"mailto:user@example.com\")?.has_authority());")]
532            /// # Ok::<_, fluent_uri::error::ParseError>(())
533            /// ```
534            #[must_use]
535            pub fn has_authority(&self) -> bool {
536                self.as_ref().has_authority()
537            }
538
539            /// Checks whether a query component is present.
540            ///
541            /// # Examples
542            ///
543            /// ```
544            #[doc = concat!("use fluent_uri::", $ty, ";")]
545            ///
546            #[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            /// # Ok::<_, fluent_uri::error::ParseError>(())
549            /// ```
550            #[must_use]
551            pub fn has_query(&self) -> bool {
552                self.as_ref().has_query()
553            }
554
555            /// Checks whether a fragment component is present.
556            ///
557            /// # Examples
558            ///
559            /// ```
560            #[doc = concat!("use fluent_uri::", $ty, ";")]
561            ///
562            #[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            /// # Ok::<_, fluent_uri::error::ParseError>(())
565            /// ```
566            #[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            /// by replacing the fragment component of `self` with the given one.
573            ///
574            /// The fragment component is removed when `opt.is_none()`.
575            ///
576            /// # Examples
577            ///
578            /// ```
579            #[doc = concat!("use fluent_uri::{encoding::EStr, ", $ty, "};")]
580            ///
581            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/\")?;")]
582            /// assert_eq!(
583            #[doc = concat!("    ", $var, ".with_fragment(Some(EStr::new_or_panic(\"fragment\"))),")]
584            ///     "http://example.com/#fragment"
585            /// );
586            ///
587            #[doc = concat!("let ", $var, " = ", $ty, "::parse(\"http://example.com/#fragment\")?;")]
588            /// assert_eq!(
589            #[doc = concat!("    ", $var, ".with_fragment(None),")]
590            ///     "http://example.com/"
591            /// );
592            /// # Ok::<_, fluent_uri::error::ParseError>(())
593            /// ```
594            #[must_use]
595            pub fn with_fragment(&self, opt: Option<&EStr<$FragmentE>>) -> $Ty<String> {
596                // Altering only the fragment does not change the metadata.
597                RiRef::new(self.as_ref().with_fragment(opt.map(EStr::as_str)), self.meta)
598            }
599        }
600
601        impl $Ty<String> {
602            /// Replaces the fragment component of `self` with the given one.
603            ///
604            /// The fragment component is removed when `opt.is_none()`.
605            ///
606            /// # Examples
607            ///
608            /// ```
609            #[doc = concat!("use fluent_uri::{encoding::EStr, ", $ty, "};")]
610            ///
611            #[doc = concat!("let mut ", $var, " = ", $ty, "::parse(\"http://example.com/\")?.to_owned();")]
612            ///
613            #[doc = concat!($var, ".set_fragment(Some(EStr::new_or_panic(\"fragment\")));")]
614            #[doc = concat!("assert_eq!(", $var, ", \"http://example.com/#fragment\");")]
615            ///
616            #[doc = concat!($var, ".set_fragment(None);")]
617            #[doc = concat!("assert_eq!(", $var, ", \"http://example.com/\");")]
618            /// # Ok::<_, fluent_uri::error::ParseError>(())
619            /// ```
620            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            /// Equivalent to [`parse`](Self::parse).
701            #[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            /// Equivalent to [`parse`](Self::parse).
711            #[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            /// Equivalent to [`to_owned`](Self::to_owned).
735            #[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/// A Rust reference to a URI/IRI (reference).
804#[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    /// Converts the URI reference to a URI if it contains a scheme.
1071    UriRef if ensure_has_scheme => Uri
1072    /// Converts the IRI to a URI if it is ASCII.
1073    Iri if ensure_ascii => Uri
1074    /// Converts the IRI reference to a URI if it contains a scheme and is ASCII.
1075    IriRef if ensure_has_scheme && ensure_ascii => Uri
1076    /// Converts the IRI reference to a URI reference if it is ASCII.
1077    IriRef if ensure_ascii => UriRef
1078    /// Converts the IRI reference to an IRI if it contains a scheme.
1079    IriRef if ensure_has_scheme => Iri
1080}
1081
1082impl<T: Bos<str>> Iri<T> {
1083    /// Converts the IRI to a URI by percent-encoding non-ASCII characters.
1084    ///
1085    /// Punycode encoding is **not** performed during conversion.
1086    ///
1087    /// # Examples
1088    ///
1089    /// ```
1090    /// use fluent_uri::Iri;
1091    ///
1092    /// let iri = Iri::parse("http://www.example.org/résumé.html").unwrap();
1093    /// assert_eq!(iri.to_uri(), "http://www.example.org/r%C3%A9sum%C3%A9.html");
1094    ///
1095    /// let iri = Iri::parse("http://résumé.example.org").unwrap();
1096    /// assert_eq!(iri.to_uri(), "http://r%C3%A9sum%C3%A9.example.org");
1097    /// ```
1098    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    /// Converts the IRI reference to a URI reference by percent-encoding non-ASCII characters.
1105    ///
1106    /// Punycode encoding is **not** performed during conversion.
1107    ///
1108    /// # Examples
1109    ///
1110    /// ```
1111    /// use fluent_uri::IriRef;
1112    ///
1113    /// let iri_ref = IriRef::parse("résumé.html").unwrap();
1114    /// assert_eq!(iri_ref.to_uri_ref(), "r%C3%A9sum%C3%A9.html");
1115    ///
1116    /// let iri_ref = IriRef::parse("//résumé.example.org").unwrap();
1117    /// assert_eq!(iri_ref.to_uri_ref(), "//r%C3%A9sum%C3%A9.example.org");
1118    /// ```
1119    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}