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}