Skip to main content

iri_string/types/generic/
reference.rs

1//! IRI reference.
2
3#[cfg(feature = "alloc")]
4use alloc::collections::TryReserveError;
5#[cfg(all(feature = "alloc", not(feature = "std")))]
6use alloc::string::String;
7
8use crate::components::AuthorityComponents;
9#[cfg(feature = "alloc")]
10use crate::mask_password::password_range_to_hide;
11use crate::mask_password::PasswordMasked;
12use crate::normalize::Normalized;
13use crate::parser::trusted as trusted_parser;
14#[cfg(feature = "alloc")]
15use crate::raw;
16use crate::resolve::FixedBaseResolver;
17use crate::spec::Spec;
18use crate::types::{RiAbsoluteStr, RiFragmentStr, RiQueryStr, RiRelativeStr, RiStr};
19#[cfg(feature = "alloc")]
20use crate::types::{RiRelativeString, RiString};
21use crate::validate::iri_reference;
22
23define_custom_string_slice! {
24    /// A borrowed string of an absolute IRI possibly with fragment part.
25    ///
26    /// This corresponds to [`IRI-reference` rule] in [RFC 3987]
27    /// (and [`URI-reference` rule] in [RFC 3986]).
28    /// The rule for `IRI-reference` is `IRI / irelative-ref`.
29    /// In other words, this is union of [`RiStr`] and [`RiRelativeStr`].
30    ///
31    /// # Valid values
32    ///
33    /// This type can have an IRI reference (which can be absolute or relative).
34    ///
35    /// ```
36    /// # use iri_string::types::IriReferenceStr;
37    /// assert!(IriReferenceStr::new("https://user:pass@example.com:8080").is_ok());
38    /// assert!(IriReferenceStr::new("https://example.com/").is_ok());
39    /// assert!(IriReferenceStr::new("https://example.com/foo?bar=baz").is_ok());
40    /// assert!(IriReferenceStr::new("https://example.com/foo?bar=baz#qux").is_ok());
41    /// assert!(IriReferenceStr::new("foo:bar").is_ok());
42    /// assert!(IriReferenceStr::new("foo:").is_ok());
43    /// // `foo://.../` below are all allowed. See the crate documentation for detail.
44    /// assert!(IriReferenceStr::new("foo:/").is_ok());
45    /// assert!(IriReferenceStr::new("foo://").is_ok());
46    /// assert!(IriReferenceStr::new("foo:///").is_ok());
47    /// assert!(IriReferenceStr::new("foo:////").is_ok());
48    /// assert!(IriReferenceStr::new("foo://///").is_ok());
49    /// assert!(IriReferenceStr::new("foo/bar").is_ok());
50    /// assert!(IriReferenceStr::new("/foo/bar").is_ok());
51    /// assert!(IriReferenceStr::new("//foo/bar").is_ok());
52    /// assert!(IriReferenceStr::new("#foo").is_ok());
53    /// ```
54    ///
55    /// Some characters and sequences cannot used in an IRI reference.
56    ///
57    /// ```
58    /// # use iri_string::types::IriReferenceStr;
59    /// // `<` and `>` cannot directly appear in an IRI reference.
60    /// assert!(IriReferenceStr::new("<not allowed>").is_err());
61    /// // Broken percent encoding cannot appear in an IRI reference.
62    /// assert!(IriReferenceStr::new("%").is_err());
63    /// assert!(IriReferenceStr::new("%GG").is_err());
64    /// ```
65    ///
66    /// [RFC 3986]: https://www.rfc-editor.org/rfc/rfc3986.html
67    /// [RFC 3987]: https://www.rfc-editor.org/rfc/rfc3987.html
68    /// [`IRI-reference` rule]: https://www.rfc-editor.org/rfc/rfc3987.html#section-2.2
69    /// [`URI-reference` rule]: https://www.rfc-editor.org/rfc/rfc3986.html#section-4.1
70    /// [`RiRelativeStr`]: struct.RiRelativeStr.html
71    /// [`RiStr`]: struct.RiStr.html
72    struct RiReferenceStr {
73        validator = iri_reference,
74        expecting_msg = "IRI reference string",
75    }
76}
77
78#[cfg(feature = "alloc")]
79define_custom_string_owned! {
80    /// An owned string of an absolute IRI possibly with fragment part.
81    ///
82    /// This corresponds to [`IRI-reference` rule] in [RFC 3987]
83    /// (and [`URI-reference` rule] in [RFC 3986]).
84    /// The rule for `IRI-reference` is `IRI / irelative-ref`.
85    /// In other words, this is union of [`RiString`] and [`RiRelativeString`].
86    ///
87    /// For details, see the document for [`RiReferenceStr`].
88    ///
89    /// Enabled by `alloc` or `std` feature.
90    ///
91    /// [RFC 3986]: https://www.rfc-editor.org/rfc/rfc3986.html
92    /// [RFC 3987]: https://www.rfc-editor.org/rfc/rfc3987.html
93    /// [`IRI-reference` rule]: https://www.rfc-editor.org/rfc/rfc3987.html#section-2.2
94    /// [`URI-reference` rule]: https://www.rfc-editor.org/rfc/rfc3986.html#section-4.1
95    /// [`RiReferenceStr`]: struct.RiReferenceString.html
96    /// [`RiRelativeString`]: struct.RiRelativeString.html
97    /// [`RiString`]: struct.RiString.html
98    struct RiReferenceString {
99        validator = iri_reference,
100        slice = RiReferenceStr,
101        expecting_msg = "IRI reference string",
102    }
103}
104
105impl<S: Spec> RiReferenceStr<S> {
106    /// Returns the string as [`&RiStr`][`RiStr`], if it is valid as an IRI.
107    ///
108    /// If it is not an IRI, then [`&RiRelativeStr`][`RiRelativeStr`] is returned as `Err(_)`.
109    ///
110    /// [`RiRelativeStr`]: struct.RiRelativeStr.html
111    /// [`RiStr`]: struct.RiStr.html
112    pub fn to_iri(&self) -> Result<&RiStr<S>, &RiRelativeStr<S>> {
113        // Check with `IRI` rule first, because the syntax rule for `IRI-reference` is
114        // `IRI / irelative-ref`.
115        //
116        // > Some productions are ambiguous. The "first-match-wins" (a.k.a.
117        // > "greedy") algorithm applies. For details, see [RFC3986].
118        // >
119        // > --- <https://www.rfc-editor.org/rfc/rfc3987.html#section-2.2>.
120
121        let s = self.as_str();
122        // TODO: Just checking `scheme:` is enough.
123        if trusted_parser::extract_scheme(s).is_some() {
124            // Has a scheme followed by a colon. An IRI.
125            // SAFETY: an IRI reference with scheme is an absolute IRI.
126            // See the RFC 3987 syntax rule `IRI-reference = IRI / irelative-ref`.
127            Ok(unsafe {
128                RiStr::<S>::new_unchecked_justified(
129                    s,
130                    "an IRI reference with a scheme must be a valid non-relative IRI",
131                )
132            })
133        } else {
134            // Has no scheme. A relative IRI reference.
135            // SAFETY: if an IRI reference is not an IRI, then it is a relative
136            // iri reference. See the RFC 3987 syntax rule
137            // `IRI-reference = IRI / irelative-ref`.
138            Err(unsafe {
139                RiRelativeStr::<S>::new_unchecked_justified(
140                    s,
141                    "an IRI reference without a scheme must be a valid relative IRI",
142                )
143            })
144        }
145    }
146
147    /// Returns the string as [`&RiRelativeStr`][`RiRelativeStr`], if it is valid as an IRI.
148    ///
149    /// If it is not an IRI, then [`&RiStr`][`RiStr`] is returned as `Err(_)`.
150    ///
151    /// [`RiRelativeStr`]: struct.RiRelativeStr.html
152    /// [`RiStr`]: struct.RiStr.html
153    pub fn to_relative_iri(&self) -> Result<&RiRelativeStr<S>, &RiStr<S>> {
154        match self.to_iri() {
155            Ok(iri) => Err(iri),
156            Err(relative) => Ok(relative),
157        }
158    }
159
160    /// Returns resolved IRI against the given base IRI.
161    ///
162    /// For IRI reference resolution output examples, see [RFC 3986 section 5.4].
163    ///
164    /// If you are going to resolve multiple references against the common base,
165    /// consider using [`FixedBaseResolver`].
166    ///
167    /// # Strictness
168    ///
169    /// The IRI parsers provided by this crate is strict (e.g. `http:g` is
170    /// always interpreted as a composition of the scheme `http` and the path
171    /// `g`), so backward compatible parsing and resolution are not provided.
172    /// About parser and resolver strictness, see [RFC 3986 section 5.4.2]:
173    ///
174    /// > Some parsers allow the scheme name to be present in a relative
175    /// > reference if it is the same as the base URI scheme. This is considered
176    /// > to be a loophole in prior specifications of partial URI
177    /// > [RFC1630](https://www.rfc-editor.org/rfc/rfc1630.html). Its use should be
178    /// > avoided but is allowed for backward compatibility.
179    /// >
180    /// > --- <https://www.rfc-editor.org/rfc/rfc3986.html#section-5.4.2>
181    ///
182    /// # Failures
183    ///
184    /// This method itself does not fail, but IRI resolution without WHATWG URL
185    /// Standard serialization can fail in some minor cases.
186    ///
187    /// To see examples of such unresolvable IRIs, visit the documentation
188    /// for [`normalize`][`crate::normalize`] module.
189    ///
190    /// [RFC 3986 section 5.4]: https://www.rfc-editor.org/rfc/rfc3986.html#section-5.4
191    /// [RFC 3986 section 5.4.2]: https://www.rfc-editor.org/rfc/rfc3986.html#section-5.4.2
192    pub fn resolve_against<'a>(&'a self, base: &'a RiAbsoluteStr<S>) -> Normalized<'a, RiStr<S>> {
193        FixedBaseResolver::new(base).resolve(self.as_ref())
194    }
195
196    /// Returns the proxy to the IRI with password masking feature.
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// # use iri_string::validate::Error;
202    /// # #[cfg(feature = "alloc")] {
203    /// use iri_string::format::ToDedicatedString;
204    /// use iri_string::types::IriReferenceStr;
205    ///
206    /// let iri = IriReferenceStr::new("http://user:password@example.com/path?query")?;
207    /// let masked = iri.mask_password();
208    /// assert_eq!(masked.to_dedicated_string(), "http://user:@example.com/path?query");
209    ///
210    /// assert_eq!(
211    ///     masked.replace_password("${password}").to_string(),
212    ///     "http://user:${password}@example.com/path?query"
213    /// );
214    /// # }
215    /// # Ok::<_, Error>(())
216    /// ```
217    #[inline]
218    #[must_use]
219    pub fn mask_password(&self) -> PasswordMasked<'_, Self> {
220        PasswordMasked::new(self)
221    }
222}
223
224/// Components getters.
225impl<S: Spec> RiReferenceStr<S> {
226    /// Returns the scheme.
227    ///
228    /// The following colon is truncated.
229    ///
230    /// # Examples
231    ///
232    /// ```
233    /// # use iri_string::validate::Error;
234    /// use iri_string::types::IriReferenceStr;
235    ///
236    /// let iri = IriReferenceStr::new("http://example.com/pathpath?queryquery#fragfrag")?;
237    /// assert_eq!(iri.scheme_str(), Some("http"));
238    /// # Ok::<_, Error>(())
239    /// ```
240    ///
241    /// ```
242    /// # use iri_string::validate::Error;
243    /// use iri_string::types::IriReferenceStr;
244    ///
245    /// let iri = IriReferenceStr::new("foo/bar:baz")?;
246    /// assert_eq!(iri.scheme_str(), None);
247    /// # Ok::<_, Error>(())
248    /// ```
249    #[inline]
250    #[must_use]
251    pub fn scheme_str(&self) -> Option<&str> {
252        trusted_parser::extract_scheme(self.as_str())
253    }
254
255    /// Returns the authority.
256    ///
257    /// The leading `//` is truncated.
258    ///
259    /// # Examples
260    ///
261    /// ```
262    /// # use iri_string::validate::Error;
263    /// use iri_string::types::IriReferenceStr;
264    ///
265    /// let iri = IriReferenceStr::new("http://example.com/pathpath?queryquery#fragfrag")?;
266    /// assert_eq!(iri.authority_str(), Some("example.com"));
267    /// # Ok::<_, Error>(())
268    /// ```
269    ///
270    /// ```
271    /// # use iri_string::validate::Error;
272    /// use iri_string::types::IriReferenceStr;
273    ///
274    /// let iri = IriReferenceStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
275    /// assert_eq!(iri.authority_str(), None);
276    /// # Ok::<_, Error>(())
277    /// ```
278    ///
279    /// ```
280    /// # use iri_string::validate::Error;
281    /// use iri_string::types::IriReferenceStr;
282    ///
283    /// let iri = IriReferenceStr::new("foo/bar:baz")?;
284    /// assert_eq!(iri.authority_str(), None);
285    /// # Ok::<_, Error>(())
286    /// ```
287    #[inline]
288    #[must_use]
289    pub fn authority_str(&self) -> Option<&str> {
290        trusted_parser::extract_authority(self.as_str())
291    }
292
293    /// Returns the path.
294    ///
295    /// # Examples
296    ///
297    /// ```
298    /// # use iri_string::validate::Error;
299    /// use iri_string::types::IriReferenceStr;
300    ///
301    /// let iri = IriReferenceStr::new("http://example.com/pathpath?queryquery#fragfrag")?;
302    /// assert_eq!(iri.path_str(), "/pathpath");
303    /// # Ok::<_, Error>(())
304    /// ```
305    ///
306    /// ```
307    /// # use iri_string::validate::Error;
308    /// use iri_string::types::IriReferenceStr;
309    ///
310    /// let iri = IriReferenceStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
311    /// assert_eq!(iri.path_str(), "uuid:10db315b-fcd1-4428-aca8-15babc9a2da2");
312    /// # Ok::<_, Error>(())
313    /// ```
314    ///
315    /// ```
316    /// # use iri_string::validate::Error;
317    /// use iri_string::types::IriReferenceStr;
318    ///
319    /// let iri = IriReferenceStr::new("foo/bar:baz")?;
320    /// assert_eq!(iri.path_str(), "foo/bar:baz");
321    /// # Ok::<_, Error>(())
322    /// ```
323    #[inline]
324    #[must_use]
325    pub fn path_str(&self) -> &str {
326        trusted_parser::extract_path(self.as_str())
327    }
328
329    /// Returns the query.
330    ///
331    /// The leading question mark (`?`) is truncated.
332    ///
333    /// # Examples
334    ///
335    /// ```
336    /// # use iri_string::validate::Error;
337    /// use iri_string::types::{IriQueryStr, IriReferenceStr};
338    ///
339    /// let iri = IriReferenceStr::new("http://example.com/pathpath?queryquery#fragfrag")?;
340    /// let query = IriQueryStr::new("queryquery")?;
341    /// assert_eq!(iri.query(), Some(query));
342    /// # Ok::<_, Error>(())
343    /// ```
344    ///
345    /// ```
346    /// # use iri_string::validate::Error;
347    /// use iri_string::types::IriReferenceStr;
348    ///
349    /// let iri = IriReferenceStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
350    /// assert_eq!(iri.query(), None);
351    /// # Ok::<_, Error>(())
352    /// ```
353    ///
354    /// ```
355    /// # use iri_string::validate::Error;
356    /// use iri_string::types::{IriQueryStr, IriReferenceStr};
357    ///
358    /// let iri = IriReferenceStr::new("foo/bar:baz?")?;
359    /// let query = IriQueryStr::new("")?;
360    /// assert_eq!(iri.query(), Some(query));
361    /// # Ok::<_, Error>(())
362    /// ```
363    #[inline]
364    #[must_use]
365    pub fn query(&self) -> Option<&RiQueryStr<S>> {
366        trusted_parser::extract_query(self.as_str()).map(|query| {
367            // SAFETY: `extract_query` returns the query part of an IRI, and the
368            // returned string should have only valid characters since is the
369            // substring of the source IRI.
370            unsafe {
371                RiQueryStr::new_unchecked_justified(
372                    query,
373                    "query in a valid IRI reference must also be valid",
374                )
375            }
376        })
377    }
378
379    /// Returns the query as a raw string slice.
380    ///
381    /// The leading question mark (`?`) is truncated.
382    ///
383    /// # Examples
384    ///
385    /// ```
386    /// # use iri_string::validate::Error;
387    /// use iri_string::types::IriReferenceStr;
388    ///
389    /// let iri = IriReferenceStr::new("http://example.com/pathpath?queryquery#fragfrag")?;
390    /// assert_eq!(iri.query_str(), Some("queryquery"));
391    /// # Ok::<_, Error>(())
392    /// ```
393    ///
394    /// ```
395    /// # use iri_string::validate::Error;
396    /// use iri_string::types::IriReferenceStr;
397    ///
398    /// let iri = IriReferenceStr::new("urn:uuid:10db315b-fcd1-4428-aca8-15babc9a2da2")?;
399    /// assert_eq!(iri.query_str(), None);
400    /// # Ok::<_, Error>(())
401    /// ```
402    ///
403    /// ```
404    /// # use iri_string::validate::Error;
405    /// use iri_string::types::IriReferenceStr;
406    ///
407    /// let iri = IriReferenceStr::new("foo/bar:baz?")?;
408    /// assert_eq!(iri.query_str(), Some(""));
409    /// # Ok::<_, Error>(())
410    /// ```
411    #[inline]
412    #[must_use]
413    pub fn query_str(&self) -> Option<&str> {
414        trusted_parser::extract_query(self.as_str())
415    }
416
417    /// Returns the fragment part if exists.
418    ///
419    /// A leading `#` character is truncated if the fragment part exists.
420    ///
421    /// # Examples
422    ///
423    /// If the IRI has a fragment part, `Some(_)` is returned.
424    ///
425    /// ```
426    /// # use iri_string::{spec::IriSpec, types::{IriFragmentStr, IriReferenceStr}, validate::Error};
427    /// let iri = IriReferenceStr::new("foo://bar/baz?qux=quux#corge")?;
428    /// let fragment = IriFragmentStr::new("corge")?;
429    /// assert_eq!(iri.fragment(), Some(fragment));
430    /// # Ok::<_, Error>(())
431    /// ```
432    ///
433    /// ```
434    /// # use iri_string::{spec::IriSpec, types::{IriFragmentStr, IriReferenceStr}, validate::Error};
435    /// let iri = IriReferenceStr::new("#foo")?;
436    /// let fragment = IriFragmentStr::new("foo")?;
437    /// assert_eq!(iri.fragment(), Some(fragment));
438    /// # Ok::<_, Error>(())
439    /// ```
440    ///
441    /// When the fragment part exists but is empty string, `Some(_)` is returned.
442    ///
443    /// ```
444    /// # use iri_string::{spec::IriSpec, types::{IriFragmentStr, IriReferenceStr}, validate::Error};
445    /// let iri = IriReferenceStr::new("foo://bar/baz?qux=quux#")?;
446    /// let fragment = IriFragmentStr::new("")?;
447    /// assert_eq!(iri.fragment(), Some(fragment));
448    /// # Ok::<_, Error>(())
449    /// ```
450    ///
451    /// ```
452    /// # use iri_string::{spec::IriSpec, types::{IriFragmentStr, IriReferenceStr}, validate::Error};
453    /// let iri = IriReferenceStr::new("#")?;
454    /// let fragment = IriFragmentStr::new("")?;
455    /// assert_eq!(iri.fragment(), Some(fragment));
456    /// # Ok::<_, Error>(())
457    /// ```
458    ///
459    /// If the IRI has no fragment, `None` is returned.
460    ///
461    /// ```
462    /// # use iri_string::{spec::IriSpec, types::IriReferenceStr, validate::Error};
463    /// let iri = IriReferenceStr::new("foo://bar/baz?qux=quux")?;
464    /// assert_eq!(iri.fragment(), None);
465    /// # Ok::<_, Error>(())
466    /// ```
467    ///
468    /// ```
469    /// # use iri_string::{spec::IriSpec, types::IriReferenceStr, validate::Error};
470    /// let iri = IriReferenceStr::new("")?;
471    /// assert_eq!(iri.fragment(), None);
472    /// # Ok::<_, Error>(())
473    /// ```
474    #[must_use]
475    pub fn fragment(&self) -> Option<&RiFragmentStr<S>> {
476        trusted_parser::extract_fragment(self.as_str()).map(|fragment| {
477            // SAFETY: `extract_fragment` returns the fragment part of an IRI,
478            // and the returned string should have only valid characters since
479            // is the substring of the source IRI.
480            unsafe {
481                RiFragmentStr::new_unchecked_justified(
482                    fragment,
483                    "fragment in a valid IRI reference must also be valid",
484                )
485            }
486        })
487    }
488
489    /// Returns the fragment part as a raw string slice if exists.
490    ///
491    /// A leading `#` character is truncated if the fragment part exists.
492    ///
493    /// # Examples
494    ///
495    /// If the IRI has a fragment part, `Some(_)` is returned.
496    ///
497    /// ```
498    /// # use iri_string::{spec::IriSpec, types::IriReferenceStr, validate::Error};
499    /// let iri = IriReferenceStr::new("foo://bar/baz?qux=quux#corge")?;
500    /// assert_eq!(iri.fragment_str(), Some("corge"));
501    /// # Ok::<_, Error>(())
502    /// ```
503    ///
504    /// ```
505    /// # use iri_string::{spec::IriSpec, types::IriReferenceStr, validate::Error};
506    /// let iri = IriReferenceStr::new("#foo")?;
507    /// assert_eq!(iri.fragment_str(), Some("foo"));
508    /// # Ok::<_, Error>(())
509    /// ```
510    ///
511    /// When the fragment part exists but is empty string, `Some(_)` is returned.
512    ///
513    /// ```
514    /// # use iri_string::{spec::IriSpec, types::IriReferenceStr, validate::Error};
515    /// let iri = IriReferenceStr::new("foo://bar/baz?qux=quux#")?;
516    /// assert_eq!(iri.fragment_str(), Some(""));
517    /// # Ok::<_, Error>(())
518    /// ```
519    ///
520    /// ```
521    /// # use iri_string::{spec::IriSpec, types::IriReferenceStr, validate::Error};
522    /// let iri = IriReferenceStr::new("#")?;
523    /// assert_eq!(iri.fragment_str(), Some(""));
524    /// # Ok::<_, Error>(())
525    /// ```
526    ///
527    /// If the IRI has no fragment, `None` is returned.
528    ///
529    /// ```
530    /// # use iri_string::{spec::IriSpec, types::IriReferenceStr, validate::Error};
531    /// let iri = IriReferenceStr::new("foo://bar/baz?qux=quux")?;
532    /// assert_eq!(iri.fragment(), None);
533    /// # Ok::<_, Error>(())
534    /// ```
535    ///
536    /// ```
537    /// # use iri_string::{spec::IriSpec, types::IriReferenceStr, validate::Error};
538    /// let iri = IriReferenceStr::new("")?;
539    /// assert_eq!(iri.fragment(), None);
540    /// # Ok::<_, Error>(())
541    /// ```
542    #[must_use]
543    pub fn fragment_str(&self) -> Option<&str> {
544        trusted_parser::extract_fragment(self.as_str())
545    }
546
547    /// Returns the authority components.
548    ///
549    /// # Examples
550    ///
551    /// ```
552    /// # use iri_string::validate::Error;
553    /// use iri_string::types::IriReferenceStr;
554    ///
555    /// let iri = IriReferenceStr::new("http://user:pass@example.com:8080/pathpath?queryquery")?;
556    /// let authority = iri.authority_components()
557    ///     .expect("authority is available");
558    /// assert_eq!(authority.userinfo(), Some("user:pass"));
559    /// assert_eq!(authority.host(), "example.com");
560    /// assert_eq!(authority.port(), Some("8080"));
561    /// # Ok::<_, Error>(())
562    /// ```
563    ///
564    /// ```
565    /// # use iri_string::validate::Error;
566    /// use iri_string::types::IriReferenceStr;
567    ///
568    /// let iri = IriReferenceStr::new("foo//bar:baz")?;
569    /// assert_eq!(iri.authority_str(), None);
570    /// # Ok::<_, Error>(())
571    /// ```
572    #[inline]
573    #[must_use]
574    pub fn authority_components(&self) -> Option<AuthorityComponents<'_>> {
575        AuthorityComponents::from_iri(self)
576    }
577}
578
579#[cfg(feature = "alloc")]
580impl<S: Spec> RiReferenceString<S> {
581    /// Returns the string as [`RiString`], if it is valid as an IRI.
582    ///
583    /// If it is not an IRI, then [`RiRelativeString`] is returned as `Err(_)`.
584    ///
585    /// [`RiRelativeString`]: struct.RiRelativeString.html
586    /// [`RiString`]: struct.RiString.html
587    pub fn into_iri(self) -> Result<RiString<S>, RiRelativeString<S>> {
588        let s: String = self.into();
589        // Check with `IRI` rule first, because of the syntax.
590        //
591        // > Some productions are ambiguous. The "first-match-wins" (a.k.a.
592        // > "greedy") algorithm applies. For details, see [RFC3986].
593        // >
594        // > --- <https://www.rfc-editor.org/rfc/rfc3987.html#section-2.2>.
595        if trusted_parser::extract_scheme(&s).is_some() {
596            // SAFETY: an IRI reference with a scheme is a non-relative IRI.
597            Ok(unsafe {
598                RiString::new_unchecked_justified(
599                    s,
600                    "IRI reference with a scheme must be a non-relative IRI reference",
601                )
602            })
603        } else {
604            // SAFETY: if an IRI reference is not an IRI, then it is a relative IRI.
605            // See the RFC 3987 syntax rule `IRI-reference = IRI / irelative-ref`.
606            Err(unsafe {
607                RiRelativeString::new_unchecked_justified(
608                    s,
609                    "non-absolute IRI reference must be a relative IRI reference",
610                )
611            })
612        }
613    }
614
615    /// Returns the string as [`RiRelativeString`], if it is valid as an IRI.
616    ///
617    /// If it is not an IRI, then [`RiString`] is returned as `Err(_)`.
618    ///
619    /// [`RiRelativeString`]: struct.RiRelativeString.html
620    /// [`RiString`]: struct.RiString.html
621    pub fn into_relative_iri(self) -> Result<RiRelativeString<S>, RiString<S>> {
622        match self.into_iri() {
623            Ok(iri) => Err(iri),
624            Err(relative) => Ok(relative),
625        }
626    }
627
628    /// Sets the fragment part to the given string.
629    ///
630    /// Removes fragment part (and following `#` character) if `None` is given.
631    pub fn set_fragment(&mut self, fragment: Option<&RiFragmentStr<S>>) {
632        raw::set_fragment(&mut self.inner, fragment.map(AsRef::as_ref));
633        debug_assert_eq!(Self::validate(&self.inner), Ok(()));
634    }
635
636    /// Removes the password completely (including separator colon) from `self` even if it is empty.
637    ///
638    /// # Examples
639    ///
640    /// ```
641    /// # use iri_string::validate::Error;
642    /// # #[cfg(feature = "alloc")] {
643    /// use iri_string::types::IriReferenceString;
644    ///
645    /// let mut iri = IriReferenceString::try_from("http://user:password@example.com/path?query")?;
646    /// iri.remove_password_inline();
647    /// assert_eq!(iri, "http://user@example.com/path?query");
648    /// # }
649    /// # Ok::<_, Error>(())
650    /// ```
651    ///
652    /// Even if the password is empty, the password and separator will be removed.
653    ///
654    /// ```
655    /// # use iri_string::validate::Error;
656    /// # #[cfg(feature = "alloc")] {
657    /// use iri_string::types::IriReferenceString;
658    ///
659    /// let mut iri = IriReferenceString::try_from("http://user:@example.com/path?query")?;
660    /// iri.remove_password_inline();
661    /// assert_eq!(iri, "http://user@example.com/path?query");
662    /// # }
663    /// # Ok::<_, Error>(())
664    /// ```
665    pub fn remove_password_inline(&mut self) {
666        let pw_range = match password_range_to_hide(self.as_slice()) {
667            Some(v) => v,
668            None => return,
669        };
670        let separator_colon = pw_range.start - 1;
671        // SAFETY: the IRI must be valid after the password component and
672        // the leading separator colon is removed.
673        unsafe {
674            let buf = self.as_inner_mut();
675            buf.drain(separator_colon..pw_range.end);
676            debug_assert_eq!(
677                Self::validate(buf),
678                Ok(()),
679                "the IRI must be valid after the password component is removed"
680            );
681        }
682    }
683
684    /// Replaces the non-empty password in `self` to the empty password.
685    ///
686    /// This leaves the separator colon if the password part was available.
687    ///
688    /// # Examples
689    ///
690    /// ```
691    /// # use iri_string::validate::Error;
692    /// # #[cfg(feature = "alloc")] {
693    /// use iri_string::types::IriReferenceString;
694    ///
695    /// let mut iri = IriReferenceString::try_from("http://user:password@example.com/path?query")?;
696    /// iri.remove_nonempty_password_inline();
697    /// assert_eq!(iri, "http://user:@example.com/path?query");
698    /// # }
699    /// # Ok::<_, Error>(())
700    /// ```
701    ///
702    /// If the password is empty, it is left as is.
703    ///
704    /// ```
705    /// # use iri_string::validate::Error;
706    /// # #[cfg(feature = "alloc")] {
707    /// use iri_string::types::IriReferenceString;
708    ///
709    /// let mut iri = IriReferenceString::try_from("http://user:@example.com/path?query")?;
710    /// iri.remove_nonempty_password_inline();
711    /// assert_eq!(iri, "http://user:@example.com/path?query");
712    /// # }
713    /// # Ok::<_, Error>(())
714    /// ```
715    pub fn remove_nonempty_password_inline(&mut self) {
716        let pw_range = match password_range_to_hide(self.as_slice()) {
717            Some(v) if !v.is_empty() => v,
718            _ => return,
719        };
720        debug_assert_eq!(
721            self.as_str().as_bytes().get(pw_range.start - 1).copied(),
722            Some(b':'),
723            "the password component must be prefixed with a separator colon"
724        );
725        // SAFETY: the IRI must be valid after the password component is
726        // replaced with the empty password.
727        unsafe {
728            let buf = self.as_inner_mut();
729            buf.drain(pw_range);
730            debug_assert_eq!(
731                Self::validate(buf),
732                Ok(()),
733                "the IRI must be valid after the password component \
734                 is replaced with the empty password"
735            );
736        }
737    }
738
739    /// Replaces the host in-place and returns the new host, if authority is not empty.
740    ///
741    /// If the IRI has no authority, returns `None` without doing nothing. Note
742    /// that an empty host is distinguished from the absence of an authority.
743    ///
744    /// If the new host is invalid (i.e., [`validate::validate_host`][`crate::validate::host`]
745    /// returns `Err(_)`), also returns `None` without doing anything.
746    fn try_replace_host_impl(
747        &mut self,
748        new_host: &'_ str,
749        replace_only_reg_name: bool,
750    ) -> Result<Option<&str>, TryReserveError> {
751        use crate::types::generic::replace_domain_impl;
752
753        let result: Result<Option<core::ops::Range<usize>>, TryReserveError>;
754        {
755            // SAFETY: Replacing the (already existing) host part with another
756            // valid host does not change the class of an IRI.
757            let strbuf = unsafe { self.as_inner_mut() };
758            result = replace_domain_impl::<S>(strbuf, new_host, replace_only_reg_name);
759            debug_assert_eq!(
760                RiReferenceStr::<S>::validate(strbuf),
761                Ok(()),
762                "replacing a host with another valid host must keep an IRI valid: raw={strbuf:?}",
763            );
764        }
765        result.map(|opt| opt.map(|range| &self.as_str()[range]))
766    }
767
768    /// Replaces the host in-place and returns the new host, if authority is not empty.
769    ///
770    /// If the IRI has no authority, returns `None` without doing nothing. Note
771    /// that an empty host is distinguished from the absence of an authority.
772    ///
773    /// If the new host is invalid (i.e., [`validate::validate_host`][`crate::validate::host`]
774    /// returns `Err(_)`), also returns `None` without doing anything.
775    ///
776    /// If you need to replace only when the host is `reg-name` (for example
777    /// when you attempt to apply IDNA encoding), use
778    /// [`try_replace_host_reg_name`][`Self::try_replace_host_reg_name`] method
779    /// instead.
780    ///
781    /// # Examples
782    ///
783    /// ```
784    /// # use iri_string::types::UriReferenceString;
785    /// let mut iri =
786    ///     UriReferenceString::try_from("https://user:pass@example.com:443/").unwrap();
787    /// let new_host_opt = iri.replace_host("www.example.com");
788    /// assert_eq!(new_host_opt, Some("www.example.com"));
789    /// assert_eq!(iri.authority_components().unwrap().host(), "www.example.com");
790    /// assert_eq!(iri, "https://user:pass@www.example.com:443/");
791    /// ```
792    pub fn replace_host(&mut self, new_host: &'_ str) -> Option<&str> {
793        self.try_replace_host(new_host)
794            .expect("failed to allocate memory when replacing the host part of an IRI")
795    }
796
797    /// Replaces the host in-place and returns the new host, if authority is not empty.
798    ///
799    /// This returns `TryReserveError` on memory allocation failure, instead of
800    /// panicking. Otherwise, this method behaves same as
801    /// [`replace_host`][`Self::replace_host`] method.
802    pub fn try_replace_host(&mut self, new_host: &'_ str) -> Result<Option<&str>, TryReserveError> {
803        self.try_replace_host_impl(new_host, false)
804    }
805
806    /// Replaces the domain name (`reg-name`) in-place and returns the new host,
807    /// if authority is not empty.
808    ///
809    /// If the IRI has no authority or the host is not a reg-name (i.e., is
810    /// neither an IP-address nor empty), returns `None` without doing nothing.
811    /// Note that an empty host is distinguished from the absence of an authority.
812    ///
813    /// If the new host is invalid (i.e., [`validate::validate_host`][`crate::validate::host`]
814    /// returns `Err(_)`), also returns `None` without doing anything.
815    ///
816    /// # Examples
817    ///
818    /// ```
819    /// # use iri_string::types::UriReferenceString;
820    /// let mut iri =
821    ///     UriReferenceString::try_from("https://user:pass@example.com:443/").unwrap();
822    /// let new_host_opt = iri.replace_host("www.example.com");
823    /// assert_eq!(new_host_opt, Some("www.example.com"));
824    /// assert_eq!(iri.authority_components().unwrap().host(), "www.example.com");
825    /// assert_eq!(iri, "https://user:pass@www.example.com:443/");
826    /// ```
827    ///
828    /// ```
829    /// # use iri_string::types::UriReferenceString;
830    /// let mut iri =
831    ///     UriReferenceString::try_from("https://192.168.0.1/").unwrap();
832    /// let new_host_opt = iri.replace_host_reg_name("localhost");
833    /// assert_eq!(new_host_opt, None, "IPv4 address is not a reg-name");
834    /// assert_eq!(iri, "https://192.168.0.1/", "won't be changed");
835    /// ```
836    ///
837    /// To apply IDNA conversion, get the domain by [`AuthorityComponents::reg_name`]
838    /// method, convert the domain, and then set it by this
839    /// `replace_host_reg_name` method.
840    ///
841    /// ```
842    /// # extern crate alloc;
843    /// # use alloc::string::String;
844    /// # use iri_string::types::IriReferenceString;
845    /// /// Converts the given into IDNA encoding.
846    /// fn conv_idna(domain: &str) -> String {
847    ///     /* ... */
848    /// #   if domain == "\u{03B1}.example.com" {
849    /// #       "xn--mxa.example.com".into()
850    /// #   } else {
851    /// #       unimplemented!()
852    /// #   }
853    /// }
854    ///
855    /// // U+03B1: GREEK SMALL LETTER ALPHA
856    /// let mut iri =
857    ///     IriReferenceString::try_from("https://\u{03B1}.example.com/").unwrap();
858    ///
859    /// let old_domain = iri
860    ///     .authority_components()
861    ///     .expect("authority is not empty")
862    ///     .reg_name()
863    ///     .expect("the host is reg-name");
864    /// assert_eq!(old_domain, "\u{03B1}.example.com");
865    ///
866    /// // Get the new host by your own.
867    /// let new_domain: String = conv_idna(old_domain);
868    /// assert_eq!(new_domain, "xn--mxa.example.com");
869    ///
870    /// let new_host_opt = iri.replace_host(&new_domain);
871    /// assert_eq!(new_host_opt, Some("xn--mxa.example.com"));
872    /// assert_eq!(iri.authority_components().unwrap().host(), "xn--mxa.example.com");
873    /// assert_eq!(iri, "https://xn--mxa.example.com/");
874    /// ```
875    pub fn replace_host_reg_name(&mut self, new_host: &'_ str) -> Option<&str> {
876        self.try_replace_host_reg_name(new_host)
877            .expect("failed to allocate memory when replacing the host part of an IRI")
878    }
879
880    /// Replaces the domain name (`reg-name`) in-place and returns the new host,
881    /// if authority is not empty.
882    ///
883    /// This returns `TryReserveError` on memory allocation failure, instead of
884    /// panicking. Otherwise, this method behaves same as
885    /// [`replace_host_reg_name`][`Self::replace_host_reg_name`] method.
886    pub fn try_replace_host_reg_name(
887        &mut self,
888        new_host: &'_ str,
889    ) -> Result<Option<&str>, TryReserveError> {
890        self.try_replace_host_impl(new_host, true)
891    }
892}