iri_string/types/generic/
absolute.rs

1//! Absolute IRI (without fragment part).
2
3use crate::components::AuthorityComponents;
4#[cfg(feature = "alloc")]
5use crate::mask_password::password_range_to_hide;
6use crate::mask_password::PasswordMasked;
7use crate::normalize::{Error, NormalizationInput, Normalized, NormalizednessCheckMode};
8use crate::parser::trusted as trusted_parser;
9use crate::spec::Spec;
10use crate::types::{RiQueryStr, RiReferenceStr, RiStr};
11#[cfg(feature = "alloc")]
12use crate::types::{RiReferenceString, RiString};
13use crate::validate::absolute_iri;
14
15define_custom_string_slice! {
16    /// A borrowed slice of an absolute IRI without fragment part.
17    ///
18    /// This corresponds to [`absolute-IRI` rule] in [RFC 3987]
19    /// (and [`absolute-URI` rule] in [RFC 3986]).
20    /// In other words, this is [`RiStr`] without fragment part.
21    ///
22    /// If you want to accept fragment part, use [`RiStr`].
23    ///
24    /// # Valid values
25    ///
26    /// This type can have an absolute IRI without fragment part.
27    ///
28    /// ```
29    /// # use iri_string::types::IriAbsoluteStr;
30    /// assert!(IriAbsoluteStr::new("https://example.com/foo?bar=baz").is_ok());
31    /// assert!(IriAbsoluteStr::new("foo:bar").is_ok());
32    /// // Scheme `foo` and empty path.
33    /// assert!(IriAbsoluteStr::new("foo:").is_ok());
34    /// // `foo://.../` below are all allowed. See the crate documentation for detail.
35    /// assert!(IriAbsoluteStr::new("foo:/").is_ok());
36    /// assert!(IriAbsoluteStr::new("foo://").is_ok());
37    /// assert!(IriAbsoluteStr::new("foo:///").is_ok());
38    /// assert!(IriAbsoluteStr::new("foo:////").is_ok());
39    /// assert!(IriAbsoluteStr::new("foo://///").is_ok());
40    ///
41    /// ```
42    ///
43    /// Relative IRI is not allowed.
44    ///
45    /// ```
46    /// # use iri_string::types::IriAbsoluteStr;
47    /// // This is relative path.
48    /// assert!(IriAbsoluteStr::new("foo/bar").is_err());
49    /// // `/foo/bar` is an absolute path, but it is authority-relative.
50    /// assert!(IriAbsoluteStr::new("/foo/bar").is_err());
51    /// // `//foo/bar` is termed "network-path reference",
52    /// // or usually called "protocol-relative reference".
53    /// assert!(IriAbsoluteStr::new("//foo/bar").is_err());
54    /// // Empty string is not a valid absolute IRI.
55    /// assert!(IriAbsoluteStr::new("").is_err());
56    /// ```
57    ///
58    /// Fragment part (such as trailing `#foo`) is not allowed.
59    ///
60    /// ```
61    /// # use iri_string::types::IriAbsoluteStr;
62    /// // Fragment part is not allowed.
63    /// assert!(IriAbsoluteStr::new("https://example.com/foo?bar=baz#qux").is_err());
64    /// ```
65    ///
66    /// Some characters and sequences cannot used in an absolute IRI.
67    ///
68    /// ```
69    /// # use iri_string::types::IriAbsoluteStr;
70    /// // `<` and `>` cannot directly appear in an absolute IRI.
71    /// assert!(IriAbsoluteStr::new("<not allowed>").is_err());
72    /// // Broken percent encoding cannot appear in an absolute IRI.
73    /// assert!(IriAbsoluteStr::new("%").is_err());
74    /// assert!(IriAbsoluteStr::new("%GG").is_err());
75    /// ```
76    ///
77    /// [RFC 3986]: https://tools.ietf.org/html/rfc3986
78    /// [RFC 3987]: https://tools.ietf.org/html/rfc3987
79    /// [`absolute-IRI` rule]: https://tools.ietf.org/html/rfc3987#section-2.2
80    /// [`absolute-URI` rule]: https://tools.ietf.org/html/rfc3986#section-4.3
81    /// [`RiStr`]: struct.RiStr.html
82    struct RiAbsoluteStr {
83        validator = absolute_iri,
84        expecting_msg = "Absolute IRI string",
85    }
86}
87
88#[cfg(feature = "alloc")]
89define_custom_string_owned! {
90    /// An owned string of an absolute IRI without fragment part.
91    ///
92    /// This corresponds to [`absolute-IRI` rule] in [RFC 3987]
93    /// (and [`absolute-URI` rule] in [RFC 3986]).
94    /// The rule for `absolute-IRI` is `scheme ":" ihier-part [ "?" iquery ]`.
95    /// In other words, this is [`RiString`] without fragment part.
96    ///
97    /// If you want to accept fragment part, use [`RiString`].
98    ///
99    /// For details, see the document for [`RiAbsoluteStr`].
100    ///
101    /// Enabled by `alloc` or `std` feature.
102    ///
103    /// [RFC 3986]: https://tools.ietf.org/html/rfc3986
104    /// [RFC 3987]: https://tools.ietf.org/html/rfc3987
105    /// [`absolute-IRI` rule]: https://tools.ietf.org/html/rfc3987#section-2.2
106    /// [`absolute-URI` rule]: https://tools.ietf.org/html/rfc3986#section-4.3
107    /// [`RiAbsoluteStr`]: struct.RiAbsoluteStr.html
108    /// [`RiString`]: struct.RiString.html
109    struct RiAbsoluteString {
110        validator = absolute_iri,
111        slice = RiAbsoluteStr,
112        expecting_msg = "Absolute IRI string",
113    }
114}
115
116impl<S: Spec> RiAbsoluteStr<S> {
117    /// Returns Ok`(())` if the IRI is normalizable by the RFC 3986 algorithm.
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// # use iri_string::validate::Error;
123    /// use iri_string::types::IriAbsoluteStr;
124    ///
125    /// let iri = IriAbsoluteStr::new("HTTP://example.COM/foo/%2e/bar/..")?;
126    /// assert!(iri.ensure_rfc3986_normalizable().is_ok());
127    ///
128    /// let iri2 = IriAbsoluteStr::new("scheme:/..//bar")?;
129    /// // The normalization result would be `scheme://bar` according to RFC
130    /// // 3986, but it is unintended and should be treated as a failure.
131    /// // This crate automatically handles this case so that `.normalize()` won't fail.
132    /// assert!(!iri.ensure_rfc3986_normalizable().is_err());
133    /// # Ok::<_, Error>(())
134    /// ```
135    #[inline]
136    pub fn ensure_rfc3986_normalizable(&self) -> Result<(), Error> {
137        NormalizationInput::from(self).ensure_rfc3986_normalizable()
138    }
139
140    /// Returns `true` if the IRI is already normalized.
141    ///
142    /// This returns the same result as `self.normalize().to_string() == self`,
143    /// but does this more efficiently without heap allocation.
144    ///
145    /// # Examples
146    ///
147    /// ```
148    /// # use iri_string::validate::Error;
149    /// # #[cfg(feature = "alloc")] {
150    /// use iri_string::format::ToDedicatedString;
151    /// use iri_string::types::IriAbsoluteStr;
152    ///
153    /// let iri = IriAbsoluteStr::new("HTTP://example.COM/foo/./bar/%2e%2e/../baz?query")?;
154    /// assert!(!iri.is_normalized());
155    ///
156    /// let normalized = iri.normalize().to_dedicated_string();
157    /// assert_eq!(normalized, "http://example.com/baz?query");
158    /// assert!(normalized.is_normalized());
159    /// # }
160    /// # Ok::<_, Error>(())
161    /// ```
162    ///
163    /// ```
164    /// # use iri_string::validate::Error;
165    /// # #[cfg(feature = "alloc")] {
166    /// use iri_string::format::ToDedicatedString;
167    /// use iri_string::types::IriAbsoluteStr;
168    ///
169    /// let iri = IriAbsoluteStr::new("scheme:/.///foo")?;
170    /// // Already normalized.
171    /// assert!(iri.is_normalized());
172    /// # }
173    /// # Ok::<_, Error>(())
174    /// ```
175    ///
176    /// ```
177    /// # use iri_string::validate::Error;
178    /// # #[cfg(feature = "alloc")] {
179    /// use iri_string::format::ToDedicatedString;
180    /// use iri_string::types::IriAbsoluteStr;
181    ///
182    /// let iri = IriAbsoluteStr::new("scheme:relative/..//not-a-host")?;
183    /// // Default normalization algorithm assumes the path part to be NOT opaque.
184    /// assert!(!iri.is_normalized());
185    ///
186    /// let normalized = iri.normalize().to_dedicated_string();
187    /// assert_eq!(normalized, "scheme:/.//not-a-host");
188    /// # }
189    /// # Ok::<_, Error>(())
190    /// ```
191    #[inline]
192    #[must_use]
193    pub fn is_normalized(&self) -> bool {
194        trusted_parser::is_normalized::<S>(self.as_str(), NormalizednessCheckMode::Default)
195    }
196
197    /// Returns `true` if the IRI is already normalized.
198    ///
199    /// This returns the same result as
200    /// `self.ensure_rfc3986_normalizable() && (self.normalize().to_string() == self)`,
201    /// does this more efficiently without heap allocation.
202    ///
203    /// # Examples
204    ///
205    /// ```
206    /// # use iri_string::validate::Error;
207    /// # #[cfg(feature = "alloc")] {
208    /// use iri_string::format::ToDedicatedString;
209    /// use iri_string::types::IriAbsoluteStr;
210    ///
211    /// let iri = IriAbsoluteStr::new("HTTP://example.COM/foo/./bar/%2e%2e/../baz?query")?;
212    /// assert!(!iri.is_normalized_rfc3986());
213    ///
214    /// let normalized = iri.normalize().to_dedicated_string();
215    /// assert_eq!(normalized, "http://example.com/baz?query");
216    /// assert!(normalized.is_normalized_rfc3986());
217    /// # }
218    /// # Ok::<_, Error>(())
219    /// ```
220    ///
221    /// ```
222    /// # use iri_string::validate::Error;
223    /// # #[cfg(feature = "alloc")] {
224    /// use iri_string::format::ToDedicatedString;
225    /// use iri_string::types::IriAbsoluteStr;
226    ///
227    /// let iri = IriAbsoluteStr::new("scheme:/.///foo")?;
228    /// // Not normalized in the sense of RFC 3986.
229    /// assert!(!iri.is_normalized_rfc3986());
230    /// # }
231    /// # Ok::<_, Error>(())
232    /// ```
233    ///
234    /// ```
235    /// # use iri_string::validate::Error;
236    /// # #[cfg(feature = "alloc")] {
237    /// use iri_string::format::ToDedicatedString;
238    /// use iri_string::types::IriAbsoluteStr;
239    ///
240    /// let iri = IriAbsoluteStr::new("scheme:relative/..//not-a-host")?;
241    /// // RFC 3986 normalization algorithm assumes the path part to be NOT opaque.
242    /// assert!(!iri.is_normalized_rfc3986());
243    ///
244    /// let normalized = iri.normalize().to_dedicated_string();
245    /// assert_eq!(normalized, "scheme:/.//not-a-host");
246    /// # }
247    /// # Ok::<_, Error>(())
248    /// ```
249    #[inline]
250    #[must_use]
251    pub fn is_normalized_rfc3986(&self) -> bool {
252        trusted_parser::is_normalized::<S>(self.as_str(), NormalizednessCheckMode::Rfc3986)
253    }
254
255    /// Returns `true` if the IRI is already normalized in the sense of
256    /// [`normalize_but_preserve_authorityless_relative_path`] method.
257    ///
258    /// This returns the same result as
259    /// `self.normalize_but_preserve_authorityless_relative_path().to_string() == self`,
260    /// but does this more efficiently without heap allocation.
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// # use iri_string::validate::Error;
266    /// # #[cfg(feature = "alloc")] {
267    /// use iri_string::format::ToDedicatedString;
268    /// use iri_string::types::IriAbsoluteStr;
269    ///
270    /// let iri = IriAbsoluteStr::new("HTTP://example.COM/foo/./bar/%2e%2e/../baz?query")?;
271    /// assert!(!iri.is_normalized_but_authorityless_relative_path_preserved());
272    ///
273    /// let normalized = iri
274    ///     .normalize_but_preserve_authorityless_relative_path()
275    ///     .to_dedicated_string();
276    /// assert_eq!(normalized, "http://example.com/baz?query");
277    /// assert!(normalized.is_normalized());
278    /// # }
279    /// # Ok::<_, Error>(())
280    /// ```
281    ///
282    /// ```
283    /// # use iri_string::validate::Error;
284    /// # #[cfg(feature = "alloc")] {
285    /// use iri_string::format::ToDedicatedString;
286    /// use iri_string::types::IriAbsoluteStr;
287    ///
288    /// let iri = IriAbsoluteStr::new("scheme:/.///foo")?;
289    /// // Already normalized in the sense of
290    /// // `normalize_but_opaque_authorityless_relative_path()` method.
291    /// assert!(iri.is_normalized_but_authorityless_relative_path_preserved());
292    /// # }
293    /// # Ok::<_, Error>(())
294    /// ```
295    ///
296    /// ```
297    /// # use iri_string::validate::Error;
298    /// # #[cfg(feature = "alloc")] {
299    /// use iri_string::format::ToDedicatedString;
300    /// use iri_string::types::IriAbsoluteStr;
301    ///
302    /// let iri = IriAbsoluteStr::new("scheme:relative/..//not-a-host")?;
303    /// // Relative path is treated as opaque since the autority component is absent.
304    /// assert!(iri.is_normalized_but_authorityless_relative_path_preserved());
305    /// # }
306    /// # Ok::<_, Error>(())
307    /// ```
308    ///
309    /// [`normalize_but_preserve_authorityless_relative_path`]:
310    ///     `Self::normalize_but_preserve_authorityless_relative_path`
311    #[inline]
312    #[must_use]
313    pub fn is_normalized_but_authorityless_relative_path_preserved(&self) -> bool {
314        trusted_parser::is_normalized::<S>(
315            self.as_str(),
316            NormalizednessCheckMode::PreserveAuthoritylessRelativePath,
317        )
318    }
319
320    /// Returns the normalized IRI.
321    ///
322    /// # Notes
323    ///
324    /// For some abnormal IRIs, the normalization can produce semantically
325    /// incorrect string that looks syntactically valid. To avoid security
326    /// issues by this trap, the normalization algorithm by this crate
327    /// automatically applies the workaround.
328    ///
329    /// If you worry about this, test by
330    /// [`RiAbsoluteStr::ensure_rfc3986_normalizable`] method or
331    /// [`Normalized::ensure_rfc3986_normalizable`] before using the result
332    /// string.
333    ///
334    /// # Examples
335    ///
336    /// ```
337    /// # use iri_string::validate::Error;
338    /// # #[cfg(feature = "alloc")] {
339    /// use iri_string::format::ToDedicatedString;
340    /// use iri_string::types::IriAbsoluteStr;
341    ///
342    /// let iri = IriAbsoluteStr::new("HTTP://example.COM/foo/./bar/%2e%2e/../baz?query")?;
343    ///
344    /// let normalized = iri.normalize().to_dedicated_string();
345    /// assert_eq!(normalized, "http://example.com/baz?query");
346    /// # }
347    /// # Ok::<_, Error>(())
348    /// ```
349    #[inline]
350    #[must_use]
351    pub fn normalize(&self) -> Normalized<'_, Self> {
352        Normalized::from_input(NormalizationInput::from(self)).and_normalize()
353    }
354
355    /// Returns the normalized IRI, but preserving dot segments in relative path
356    /// if the authority component is absent.
357    ///
358    /// This normalization would be similar to that of [WHATWG URL Standard]
359    /// while this implementation is not guaranteed to stricly follow the spec.
360    ///
361    /// Note that this normalization algorithm is not compatible with RFC 3986
362    /// algorithm for some inputs.
363    ///
364    /// Note that case normalization and percent-encoding normalization will
365    /// still be applied to any path.
366    ///
367    /// # Examples
368    ///
369    /// ```
370    /// # use iri_string::validate::Error;
371    /// # #[cfg(feature = "alloc")] {
372    /// use iri_string::format::ToDedicatedString;
373    /// use iri_string::types::IriAbsoluteStr;
374    ///
375    /// let iri = IriAbsoluteStr::new("HTTP://example.COM/foo/./bar/%2e%2e/../baz?query")?;
376    ///
377    /// let normalized = iri
378    ///     .normalize_but_preserve_authorityless_relative_path()
379    ///     .to_dedicated_string();
380    /// assert_eq!(normalized, "http://example.com/baz?query");
381    /// # }
382    /// # Ok::<_, Error>(())
383    /// ```
384    ///
385    /// ```
386    /// # use iri_string::validate::Error;
387    /// # #[cfg(feature = "alloc")] {
388    /// use iri_string::format::ToDedicatedString;
389    /// use iri_string::types::IriAbsoluteStr;
390    ///
391    /// let iri = IriAbsoluteStr::new("scheme:relative/../f%6f%6f")?;
392    ///
393    /// let normalized = iri
394    ///     .normalize_but_preserve_authorityless_relative_path()
395    ///     .to_dedicated_string();
396    /// assert_eq!(normalized, "scheme:relative/../foo");
397    /// // `.normalize()` would normalize this to `scheme:/foo`.
398    /// # assert_eq!(iri.normalize().to_dedicated_string(), "scheme:/foo");
399    /// # }
400    /// # Ok::<_, Error>(())
401    /// ```
402    ///
403    /// [WHATWG URL Standard]: https://url.spec.whatwg.org/
404    #[inline]
405    #[must_use]
406    pub fn normalize_but_preserve_authorityless_relative_path(&self) -> Normalized<'_, Self> {
407        Normalized::from_input(NormalizationInput::from(self))
408            .and_normalize_but_preserve_authorityless_relative_path()
409    }
410
411    /// Returns the proxy to the IRI with password masking feature.
412    ///
413    /// # Examples
414    ///
415    /// ```
416    /// # use iri_string::validate::Error;
417    /// # #[cfg(feature = "alloc")] {
418    /// use iri_string::format::ToDedicatedString;
419    /// use iri_string::types::IriAbsoluteStr;
420    ///
421    /// let iri = IriAbsoluteStr::new("http://user:password@example.com/path?query")?;
422    /// let masked = iri.mask_password();
423    /// assert_eq!(masked.to_dedicated_string(), "http://user:@example.com/path?query");
424    ///
425    /// assert_eq!(
426    ///     masked.replace_password("${password}").to_string(),
427    ///     "http://user:${password}@example.com/path?query"
428    /// );
429    /// # }
430    /// # Ok::<_, Error>(())
431    /// ```
432    #[inline]
433    #[must_use]
434    pub fn mask_password(&self) -> PasswordMasked<'_, Self> {
435        PasswordMasked::new(self)
436    }
437}
438
439/// Components getters.
440impl<S: Spec> RiAbsoluteStr<S> {
441    /// Returns the scheme.
442    ///
443    /// The following colon is truncated.
444    ///
445    /// # Examples
446    ///
447    /// ```
448    /// # use iri_string::validate::Error;
449    /// use iri_string::types::IriAbsoluteStr;
450    ///
451    /// let iri = IriAbsoluteStr::new("http://example.com/pathpath?queryquery")?;
452    /// assert_eq!(iri.scheme_str(), "http");
453    /// # Ok::<_, Error>(())
454    /// ```
455    #[inline]
456    #[must_use]
457    pub fn scheme_str(&self) -> &str {
458        trusted_parser::extract_scheme_absolute(self.as_str())
459    }
460
461    /// Returns the authority.
462    ///
463    /// The leading `//` is truncated.
464    ///
465    /// # Examples
466    ///
467    /// ```
468    /// # use iri_string::validate::Error;
469    /// use iri_string::types::IriAbsoluteStr;
470    ///
471    /// let iri = IriAbsoluteStr::new("http://example.com/pathpath?queryquery")?;
472    /// assert_eq!(iri.authority_str(), Some("example.com"));
473    /// # Ok::<_, Error>(())
474    /// ```
475    ///
476    /// ```
477    /// # use iri_string::validate::Error;
478    /// use iri_string::types::IriAbsoluteStr;
479    ///
480    /// let iri = IriAbsoluteStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
481    /// assert_eq!(iri.authority_str(), None);
482    /// # Ok::<_, Error>(())
483    /// ```
484    #[inline]
485    #[must_use]
486    pub fn authority_str(&self) -> Option<&str> {
487        trusted_parser::extract_authority_absolute(self.as_str())
488    }
489
490    /// Returns the path.
491    ///
492    /// # Examples
493    ///
494    /// ```
495    /// # use iri_string::validate::Error;
496    /// use iri_string::types::IriAbsoluteStr;
497    ///
498    /// let iri = IriAbsoluteStr::new("http://example.com/pathpath?queryquery")?;
499    /// assert_eq!(iri.path_str(), "/pathpath");
500    /// # Ok::<_, Error>(())
501    /// ```
502    ///
503    /// ```
504    /// # use iri_string::validate::Error;
505    /// use iri_string::types::IriAbsoluteStr;
506    ///
507    /// let iri = IriAbsoluteStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
508    /// assert_eq!(iri.path_str(), "uuid:10db315b-fcd1-4428-aca8-15babc9a2da2");
509    /// # Ok::<_, Error>(())
510    /// ```
511    #[inline]
512    #[must_use]
513    pub fn path_str(&self) -> &str {
514        trusted_parser::extract_path_absolute(self.as_str())
515    }
516
517    /// Returns the query.
518    ///
519    /// The leading question mark (`?`) is truncated.
520    ///
521    /// # Examples
522    ///
523    /// ```
524    /// # use iri_string::validate::Error;
525    /// use iri_string::types::{IriAbsoluteStr, IriQueryStr};
526    ///
527    /// let iri = IriAbsoluteStr::new("http://example.com/pathpath?queryquery")?;
528    /// let query = IriQueryStr::new("queryquery")?;
529    /// assert_eq!(iri.query(), Some(query));
530    /// # Ok::<_, Error>(())
531    /// ```
532    ///
533    /// ```
534    /// # use iri_string::validate::Error;
535    /// use iri_string::types::IriAbsoluteStr;
536    ///
537    /// let iri = IriAbsoluteStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
538    /// assert_eq!(iri.query(), None);
539    /// # Ok::<_, Error>(())
540    /// ```
541    #[inline]
542    #[must_use]
543    pub fn query(&self) -> Option<&RiQueryStr<S>> {
544        trusted_parser::extract_query_absolute_iri(self.as_str()).map(|query| {
545            // SAFETY: `trusted_parser::extract_query_absolute_iri()` must return
546            // the query part of an IRI (including the leading `?` character),
547            // and the returned string consists of allowed characters since it
548            // is a substring of the source IRI.
549            unsafe { RiQueryStr::new_maybe_unchecked(query) }
550        })
551    }
552
553    /// Returns the query in a raw string slice.
554    ///
555    /// The leading question mark (`?`) is truncated.
556    ///
557    /// # Examples
558    ///
559    /// ```
560    /// # use iri_string::validate::Error;
561    /// use iri_string::types::IriAbsoluteStr;
562    ///
563    /// let iri = IriAbsoluteStr::new("http://example.com/pathpath?queryquery")?;
564    /// assert_eq!(iri.query_str(), Some("queryquery"));
565    /// # Ok::<_, Error>(())
566    /// ```
567    ///
568    /// ```
569    /// # use iri_string::validate::Error;
570    /// use iri_string::types::IriAbsoluteStr;
571    ///
572    /// let iri = IriAbsoluteStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
573    /// assert_eq!(iri.query_str(), None);
574    /// # Ok::<_, Error>(())
575    /// ```
576    #[inline]
577    #[must_use]
578    pub fn query_str(&self) -> Option<&str> {
579        trusted_parser::extract_query_absolute_iri(self.as_str())
580    }
581
582    /// Returns the authority components.
583    ///
584    /// # Examples
585    ///
586    /// ```
587    /// # use iri_string::validate::Error;
588    /// use iri_string::types::IriAbsoluteStr;
589    ///
590    /// let iri = IriAbsoluteStr::new("http://user:pass@example.com:8080/pathpath?queryquery")?;
591    /// let authority = iri.authority_components()
592    ///     .expect("authority is available");
593    /// assert_eq!(authority.userinfo(), Some("user:pass"));
594    /// assert_eq!(authority.host(), "example.com");
595    /// assert_eq!(authority.port(), Some("8080"));
596    /// # Ok::<_, Error>(())
597    /// ```
598    ///
599    /// ```
600    /// # use iri_string::validate::Error;
601    /// use iri_string::types::IriAbsoluteStr;
602    ///
603    /// let iri = IriAbsoluteStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
604    /// assert_eq!(iri.authority_str(), None);
605    /// # Ok::<_, Error>(())
606    /// ```
607    #[inline]
608    #[must_use]
609    pub fn authority_components(&self) -> Option<AuthorityComponents<'_>> {
610        AuthorityComponents::from_iri(self.as_ref())
611    }
612}
613
614#[cfg(feature = "alloc")]
615impl<S: Spec> RiAbsoluteString<S> {
616    /// Removes the password completely (including separator colon) from `self` even if it is empty.
617    ///
618    /// # Examples
619    ///
620    /// ```
621    /// # use iri_string::validate::Error;
622    /// # #[cfg(feature = "alloc")] {
623    /// use iri_string::types::IriAbsoluteString;
624    ///
625    /// let mut iri = IriAbsoluteString::try_from("http://user:password@example.com/path?query")?;
626    /// iri.remove_password_inline();
627    /// assert_eq!(iri, "http://user@example.com/path?query");
628    /// # }
629    /// # Ok::<_, Error>(())
630    /// ```
631    ///
632    /// Even if the password is empty, the password and separator will be removed.
633    ///
634    /// ```
635    /// # use iri_string::validate::Error;
636    /// # #[cfg(feature = "alloc")] {
637    /// use iri_string::types::IriAbsoluteString;
638    ///
639    /// let mut iri = IriAbsoluteString::try_from("http://user:@example.com/path?query")?;
640    /// iri.remove_password_inline();
641    /// assert_eq!(iri, "http://user@example.com/path?query");
642    /// # }
643    /// # Ok::<_, Error>(())
644    /// ```
645    pub fn remove_password_inline(&mut self) {
646        let pw_range = match password_range_to_hide(self.as_slice().as_ref()) {
647            Some(v) => v,
648            None => return,
649        };
650        let separator_colon = pw_range.start - 1;
651        // SAFETY: the IRI must still be valid after the password component and
652        // the leading separator colon is removed.
653        unsafe {
654            let buf = self.as_inner_mut();
655            buf.drain(separator_colon..pw_range.end);
656            debug_assert!(
657                RiAbsoluteStr::<S>::new(buf).is_ok(),
658                "[validity] the IRI must be valid after the password component is removed"
659            );
660        }
661    }
662
663    /// Replaces the non-empty password in `self` to the empty password.
664    ///
665    /// This leaves the separator colon if the password part was available.
666    ///
667    /// # Examples
668    ///
669    /// ```
670    /// # use iri_string::validate::Error;
671    /// # #[cfg(feature = "alloc")] {
672    /// use iri_string::types::IriAbsoluteString;
673    ///
674    /// let mut iri = IriAbsoluteString::try_from("http://user:password@example.com/path?query")?;
675    /// iri.remove_nonempty_password_inline();
676    /// assert_eq!(iri, "http://user:@example.com/path?query");
677    /// # }
678    /// # Ok::<_, Error>(())
679    /// ```
680    ///
681    /// If the password is empty, it is left as is.
682    ///
683    /// ```
684    /// # use iri_string::validate::Error;
685    /// # #[cfg(feature = "alloc")] {
686    /// use iri_string::types::IriAbsoluteString;
687    ///
688    /// let mut iri = IriAbsoluteString::try_from("http://user:@example.com/path?query")?;
689    /// iri.remove_nonempty_password_inline();
690    /// assert_eq!(iri, "http://user:@example.com/path?query");
691    /// # }
692    /// # Ok::<_, Error>(())
693    /// ```
694    pub fn remove_nonempty_password_inline(&mut self) {
695        let pw_range = match password_range_to_hide(self.as_slice().as_ref()) {
696            Some(v) if !v.is_empty() => v,
697            _ => return,
698        };
699        debug_assert_eq!(
700            self.as_str().as_bytes().get(pw_range.start - 1).copied(),
701            Some(b':'),
702            "[validity] the password component must be prefixed with a separator colon"
703        );
704        // SAFETY: the IRI must be valid after the password is replaced with empty string.
705        unsafe {
706            let buf = self.as_inner_mut();
707            buf.drain(pw_range);
708            debug_assert!(
709                RiAbsoluteStr::<S>::new(buf).is_ok(),
710                "[validity] the IRI must be valid after the password component is removed"
711            );
712        }
713    }
714}
715
716impl_trivial_conv_between_iri! {
717    from_slice: RiAbsoluteStr,
718    from_owned: RiAbsoluteString,
719    to_slice: RiStr,
720    to_owned: RiString,
721}
722
723impl_trivial_conv_between_iri! {
724    from_slice: RiAbsoluteStr,
725    from_owned: RiAbsoluteString,
726    to_slice: RiReferenceStr,
727    to_owned: RiReferenceString,
728}