Skip to main content

iri_string/types/generic/
relative.rs

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