iri_string/build.rs
1//! URI/IRI builder.
2//!
3//! See the documentation of [`Builder`] type.
4
5use core::fmt::{self, Display as _, Write as _};
6use core::marker::PhantomData;
7
8#[cfg(feature = "alloc")]
9use alloc::collections::TryReserveError;
10#[cfg(all(feature = "alloc", not(feature = "std")))]
11use alloc::string::ToString;
12
13use crate::format::Censored;
14#[cfg(feature = "alloc")]
15use crate::format::{ToDedicatedString, ToStringFallible};
16use crate::normalize::{self, NormalizationMode, PathCharacteristic, PctCaseNormalized};
17use crate::parser::str::{find_split, prior_byte2};
18use crate::parser::trusted as trusted_parser;
19use crate::parser::validate as parser;
20use crate::spec::Spec;
21use crate::types::{RiAbsoluteStr, RiReferenceStr, RiRelativeStr, RiStr};
22#[cfg(feature = "alloc")]
23use crate::types::{RiAbsoluteString, RiReferenceString, RiRelativeString, RiString};
24use crate::validate::{Error, ErrorKind};
25
26/// Port builder.
27///
28/// This type is intended to be created by `From` trait implementations, and
29/// to be passed to [`Builder::port`] method.
30#[derive(Debug, Clone)]
31pub struct PortBuilder<'a>(PortBuilderRepr<'a>);
32
33impl Default for PortBuilder<'_> {
34 #[inline]
35 fn default() -> Self {
36 Self(PortBuilderRepr::Empty)
37 }
38}
39
40impl From<u8> for PortBuilder<'_> {
41 #[inline]
42 fn from(v: u8) -> Self {
43 Self(PortBuilderRepr::Integer(v.into()))
44 }
45}
46
47impl From<u16> for PortBuilder<'_> {
48 #[inline]
49 fn from(v: u16) -> Self {
50 Self(PortBuilderRepr::Integer(v))
51 }
52}
53
54impl<'a> From<&'a str> for PortBuilder<'a> {
55 #[inline]
56 fn from(v: &'a str) -> Self {
57 Self(PortBuilderRepr::String(v))
58 }
59}
60
61#[cfg(feature = "alloc")]
62impl<'a> From<&'a alloc::string::String> for PortBuilder<'a> {
63 #[inline]
64 fn from(v: &'a alloc::string::String) -> Self {
65 Self(PortBuilderRepr::String(v.as_str()))
66 }
67}
68
69/// Internal representation of a port builder.
70#[derive(Debug, Clone, Copy)]
71#[non_exhaustive]
72enum PortBuilderRepr<'a> {
73 /// Empty port.
74 Empty,
75 /// Port as an integer.
76 ///
77 /// Note that RFC 3986 accepts any number of digits as a port, but
78 /// practically (at least in TCP/IP) `u16` is enough.
79 Integer(u16),
80 /// Port as a string.
81 String(&'a str),
82}
83
84/// Userinfo builder.
85///
86/// This type is intended to be created by `From` trait implementations, and
87/// to be passed to [`Builder::userinfo`] method.
88#[derive(Clone)]
89pub struct UserinfoBuilder<'a>(UserinfoRepr<'a>);
90
91impl Default for UserinfoBuilder<'_> {
92 #[inline]
93 fn default() -> Self {
94 Self(UserinfoRepr::None)
95 }
96}
97
98impl fmt::Debug for UserinfoBuilder<'_> {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 let mut debug = f.debug_struct("UserinfoBuilder");
101 if let Some((user, password)) = self.to_user_password() {
102 debug.field("user", &user);
103 // > Applications should not render as clear text any data after
104 // > the first colon (":") character found within a userinfo
105 // > subcomponent unless the data after the colon is the empty
106 // > string (indicating no password).
107 if matches!(password, None | Some("")) {
108 debug.field("password", &password);
109 } else {
110 debug.field("password", &Some(Censored));
111 }
112 }
113 debug.finish()
114 }
115}
116
117impl<'a> UserinfoBuilder<'a> {
118 /// Decomposes the userinfo into `user` and `password`.
119 #[must_use]
120 fn to_user_password(&self) -> Option<(&'a str, Option<&'a str>)> {
121 match &self.0 {
122 UserinfoRepr::None => None,
123 UserinfoRepr::Direct(s) => match find_split(s, b':') {
124 None => Some((s, None)),
125 Some((user, password)) => Some((user, Some(password))),
126 },
127 UserinfoRepr::UserPass(user, password) => Some((*user, *password)),
128 }
129 }
130}
131
132impl<'a> From<&'a str> for UserinfoBuilder<'a> {
133 #[inline]
134 fn from(direct: &'a str) -> Self {
135 Self(UserinfoRepr::Direct(direct))
136 }
137}
138
139impl<'a> From<(&'a str, &'a str)> for UserinfoBuilder<'a> {
140 #[inline]
141 fn from((user, password): (&'a str, &'a str)) -> Self {
142 Self(UserinfoRepr::UserPass(user, Some(password)))
143 }
144}
145
146impl<'a> From<(&'a str, Option<&'a str>)> for UserinfoBuilder<'a> {
147 #[inline]
148 fn from((user, password): (&'a str, Option<&'a str>)) -> Self {
149 Self(UserinfoRepr::UserPass(user, password))
150 }
151}
152
153#[cfg(feature = "alloc")]
154impl<'a> From<&'a alloc::string::String> for UserinfoBuilder<'a> {
155 #[inline]
156 fn from(v: &'a alloc::string::String) -> Self {
157 Self::from(v.as_str())
158 }
159}
160
161/// Internal representation of a userinfo builder.
162#[derive(Clone, Copy)]
163enum UserinfoRepr<'a> {
164 /// Not specified (absent).
165 None,
166 /// Direct `userinfo` content.
167 Direct(&'a str),
168 /// User name and password.
169 UserPass(&'a str, Option<&'a str>),
170}
171
172/// URI/IRI authority builder.
173#[derive(Default, Debug, Clone)]
174struct AuthorityBuilder<'a> {
175 /// Host.
176 host: HostRepr<'a>,
177 /// Port.
178 port: PortBuilder<'a>,
179 /// Userinfo.
180 userinfo: UserinfoBuilder<'a>,
181}
182
183impl AuthorityBuilder<'_> {
184 /// Writes the authority to the given formatter.
185 fn fmt_write_to<S: Spec>(&self, f: &mut fmt::Formatter<'_>, normalize: bool) -> fmt::Result {
186 match &self.userinfo.0 {
187 UserinfoRepr::None => {}
188 UserinfoRepr::Direct(userinfo) => {
189 if normalize {
190 PctCaseNormalized::<S>::new(userinfo).fmt(f)?;
191 } else {
192 userinfo.fmt(f)?;
193 }
194 f.write_char('@')?;
195 }
196 UserinfoRepr::UserPass(user, password) => {
197 if normalize {
198 PctCaseNormalized::<S>::new(user).fmt(f)?;
199 } else {
200 f.write_str(user)?;
201 }
202 if let Some(password) = password {
203 f.write_char(':')?;
204 if normalize {
205 PctCaseNormalized::<S>::new(password).fmt(f)?;
206 } else {
207 password.fmt(f)?;
208 }
209 }
210 f.write_char('@')?;
211 }
212 }
213
214 match self.host {
215 HostRepr::String(host) => {
216 if normalize {
217 normalize::normalize_host_port::<S>(f, host)?;
218 } else {
219 f.write_str(host)?;
220 }
221 }
222 #[cfg(feature = "std")]
223 HostRepr::IpAddr(ipaddr) => match ipaddr {
224 std::net::IpAddr::V4(v) => v.fmt(f)?,
225 std::net::IpAddr::V6(v) => write!(f, "[{v}]")?,
226 },
227 }
228
229 match self.port.0 {
230 PortBuilderRepr::Empty => {}
231 PortBuilderRepr::Integer(v) => write!(f, ":{v}")?,
232 PortBuilderRepr::String(v) => {
233 // Omit empty port if the normalization is enabled.
234 if !(v.is_empty() && normalize) {
235 write!(f, ":{v}")?;
236 }
237 }
238 }
239
240 Ok(())
241 }
242}
243
244/// Host representation.
245#[derive(Debug, Clone, Copy)]
246enum HostRepr<'a> {
247 /// Direct string representation.
248 String(&'a str),
249 #[cfg(feature = "std")]
250 /// Dedicated IP address type.
251 IpAddr(std::net::IpAddr),
252}
253
254impl Default for HostRepr<'_> {
255 #[inline]
256 fn default() -> Self {
257 Self::String("")
258 }
259}
260
261/// URI/IRI reference builder.
262///
263/// # Usage
264///
265/// 1. Create builder by [`Builder::new()`][`Self::new`].
266/// + Alternatively, you can create a builder from an IRI using [`From`] trait.
267/// 2. Set (or unset) components and set normalization mode as you wish.
268/// 3. Validate by [`Builder::build()`][`Self::build`] and get [`Built`] value.
269/// 4. Use [`core::fmt::Display`] trait to serialize the resulting [`Built`],
270/// or use [`From`]/[`Into`] traits to convert into an allocated string types.
271///
272/// ```
273/// # use iri_string::validate::Error;
274/// use iri_string::build::Builder;
275/// # #[cfg(not(feature = "alloc"))]
276/// # use iri_string::types::IriStr;
277/// # #[cfg(feature = "alloc")]
278/// use iri_string::types::{IriStr, IriString};
279///
280/// // 1. Create builder.
281/// let mut builder = Builder::new();
282///
283/// // 2. Set (or unset) component and normalization mode.
284/// builder.scheme("http");
285/// builder.host("example.com");
286/// builder.path("/foo/../");
287/// builder.normalize();
288///
289/// // 3. Validate and create the result.
290/// let built = builder.build::<IriStr>()?;
291///
292/// # #[cfg(feature = "alloc")] {
293/// // 4a. Serialize by `Display` trait (or `ToString`).
294/// let s = built.to_string();
295/// assert_eq!(s, "http://example.com/");
296/// # }
297///
298/// # #[cfg(feature = "alloc")] {
299/// // 4b. Convert into an allocated string types.
300/// // Thanks to pre-validation by `.build::<IriStr>()`, this conversion is infallible!
301/// let s: IriString = built.into();
302/// assert_eq!(s, "http://example.com/");
303/// # }
304///
305/// # Ok::<_, Error>(())
306/// ```
307#[derive(Default, Debug, Clone)]
308pub struct Builder<'a> {
309 /// Scheme.
310 scheme: Option<&'a str>,
311 /// Authority.
312 authority: Option<AuthorityBuilder<'a>>,
313 /// Path.
314 path: &'a str,
315 /// Query (without the leading `?`).
316 query: Option<&'a str>,
317 /// Fragment (without the leading `#`).
318 fragment: Option<&'a str>,
319 /// Normalization mode.
320 normalize: bool,
321}
322
323impl<'a> Builder<'a> {
324 /// Creates a builder with empty data.
325 ///
326 /// # Examples
327 ///
328 /// ```
329 /// # use iri_string::validate::Error;
330 /// use iri_string::build::Builder;
331 /// use iri_string::types::IriReferenceStr;
332 ///
333 /// let builder = Builder::new();
334 ///
335 /// let iri = builder.build::<IriReferenceStr>()?;
336 /// # #[cfg(feature = "alloc")] {
337 /// assert_eq!(iri.to_string(), "");
338 /// # }
339 /// # Ok::<_, Error>(())
340 /// ```
341 #[inline]
342 #[must_use]
343 pub fn new() -> Self {
344 Self::default()
345 }
346
347 /// Writes the authority to the given formatter.
348 ///
349 /// Don't expose this as public, since this method does not validate.
350 ///
351 /// # Preconditions
352 ///
353 /// The IRI string to be built should be a valid IRI reference.
354 /// Callers are responsible to validate the component values before calling
355 /// this method.
356 fn fmt_write_to<S: Spec>(
357 &self,
358 f: &mut fmt::Formatter<'_>,
359 path_is_absolute: bool,
360 ) -> fmt::Result {
361 if let Some(scheme) = self.scheme {
362 // Write the scheme.
363 if self.normalize {
364 normalize::normalize_scheme(f, scheme)?;
365 } else {
366 f.write_str(scheme)?;
367 }
368 f.write_char(':')?;
369 }
370
371 if let Some(authority) = &self.authority {
372 f.write_str("//")?;
373 authority.fmt_write_to::<S>(f, self.normalize)?;
374 }
375
376 if !self.normalize {
377 // No normalization.
378 f.write_str(self.path)?;
379 } else if self.scheme.is_some() || self.authority.is_some() || path_is_absolute {
380 // Apply full syntax-based normalization.
381 let op = normalize::NormalizationOp {
382 mode: NormalizationMode::Default,
383 };
384 normalize::PathToNormalize::from_single_path(self.path).fmt_write_normalize::<S, _>(
385 f,
386 op,
387 self.authority.is_some(),
388 )?;
389 } else {
390 // The IRI reference starts with `path` component, and the path is relative.
391 // Skip path segment normalization.
392 PctCaseNormalized::<S>::new(self.path).fmt(f)?;
393 }
394
395 if let Some(query) = self.query {
396 f.write_char('?')?;
397 if self.normalize {
398 normalize::normalize_query::<S>(f, query)?;
399 } else {
400 f.write_str(query)?;
401 }
402 }
403
404 if let Some(fragment) = self.fragment {
405 f.write_char('#')?;
406 if self.normalize {
407 normalize::normalize_fragment::<S>(f, fragment)?;
408 } else {
409 f.write_str(fragment)?;
410 }
411 }
412
413 Ok(())
414 }
415
416 /// Builds the proxy object that can be converted to the desired IRI string type.
417 ///
418 /// # Examples
419 ///
420 /// ```
421 /// # use iri_string::validate::Error;
422 /// use iri_string::build::Builder;
423 /// use iri_string::types::IriStr;
424 /// # #[cfg(feature = "alloc")]
425 /// use iri_string::types::IriString;
426 ///
427 /// let mut builder = Builder::new();
428 ///
429 /// builder.scheme("http");
430 /// builder.host("example.com");
431 /// builder.path("/foo/bar");
432 ///
433 /// let built = builder.build::<IriStr>()?;
434 ///
435 /// # #[cfg(feature = "alloc")] {
436 /// // The returned value implements `core::fmt::Display` and
437 /// // `core::string::ToString`.
438 /// assert_eq!(built.to_string(), "http://example.com/foo/bar");
439 ///
440 /// // The returned value implements `Into<{iri_owned_string_type}>`.
441 /// let iri = IriString::from(built);
442 /// // `let iri: IriString = built.into();` is also OK.
443 /// # }
444 /// # Ok::<_, Error>(())
445 /// ```
446 #[inline]
447 pub fn build<T>(self) -> Result<Built<'a, T>, Error>
448 where
449 T: ?Sized + Buildable<'a>,
450 {
451 <T as private::Sealed<'a>>::validate_builder(self)
452 }
453}
454
455// Setters does not return `&mut Self` or `Self` since it introduces needless
456// ambiguity for users.
457// For example, if setters return something and allows method chaining, can you
458// correctly explain what happens with the code below without reading document?
459//
460// ```text
461// let mut builder = Builder::new().foo("foo").bar("bar");
462// let baz = builder.baz("baz").clone().build();
463// // Should the result be foo+bar+qux, or foo+bar+baz+qux?
464// let qux = builder.qux("qux").build();
465// ```
466impl<'a> Builder<'a> {
467 /// Sets the scheme.
468 ///
469 /// # Examples
470 ///
471 /// ```
472 /// # use iri_string::validate::Error;
473 /// use iri_string::build::Builder;
474 /// use iri_string::types::IriReferenceStr;
475 ///
476 /// let mut builder = Builder::new();
477 /// builder.scheme("foo");
478 ///
479 /// let iri = builder.build::<IriReferenceStr>()?;
480 /// # #[cfg(feature = "alloc")] {
481 /// assert_eq!(iri.to_string(), "foo:");
482 /// # }
483 /// # Ok::<_, Error>(())
484 /// ```
485 #[inline]
486 pub fn scheme(&mut self, v: &'a str) {
487 self.scheme = Some(v);
488 }
489
490 /// Unsets the scheme.
491 ///
492 /// # Examples
493 ///
494 /// ```
495 /// # use iri_string::validate::Error;
496 /// use iri_string::build::Builder;
497 /// use iri_string::types::IriReferenceStr;
498 ///
499 /// let mut builder = Builder::new();
500 /// builder.scheme("foo");
501 /// builder.unset_scheme();
502 ///
503 /// let iri = builder.build::<IriReferenceStr>()?;
504 /// # #[cfg(feature = "alloc")] {
505 /// assert_eq!(iri.to_string(), "");
506 /// # }
507 /// # Ok::<_, Error>(())
508 /// ```
509 #[inline]
510 pub fn unset_scheme(&mut self) {
511 self.scheme = None;
512 }
513
514 /// Sets the path.
515 ///
516 /// Note that no methods are provided to "unset" path since every IRI
517 /// references has a path component (although it can be empty).
518 /// If you want to "unset" the path, just set the empty string.
519 ///
520 /// # Examples
521 ///
522 /// ```
523 /// # use iri_string::validate::Error;
524 /// use iri_string::build::Builder;
525 /// use iri_string::types::IriReferenceStr;
526 ///
527 /// let mut builder = Builder::new();
528 /// builder.path("foo/bar");
529 ///
530 /// let iri = builder.build::<IriReferenceStr>()?;
531 /// # #[cfg(feature = "alloc")] {
532 /// assert_eq!(iri.to_string(), "foo/bar");
533 /// # }
534 /// # Ok::<_, Error>(())
535 /// ```
536 #[inline]
537 pub fn path(&mut self, v: &'a str) {
538 self.path = v;
539 }
540
541 /// Initializes the authority builder.
542 #[inline]
543 fn authority_builder(&mut self) -> &mut AuthorityBuilder<'a> {
544 self.authority.get_or_insert_with(AuthorityBuilder::default)
545 }
546
547 /// Unsets the authority.
548 ///
549 /// # Examples
550 ///
551 /// ```
552 /// # use iri_string::validate::Error;
553 /// use iri_string::build::Builder;
554 /// use iri_string::types::IriReferenceStr;
555 ///
556 /// let mut builder = Builder::new();
557 /// builder.host("example.com");
558 /// builder.unset_authority();
559 ///
560 /// let iri = builder.build::<IriReferenceStr>()?;
561 /// # #[cfg(feature = "alloc")] {
562 /// assert_eq!(iri.to_string(), "");
563 /// # }
564 /// # Ok::<_, Error>(())
565 /// ```
566 #[inline]
567 pub fn unset_authority(&mut self) {
568 self.authority = None;
569 }
570
571 /// Sets the userinfo.
572 ///
573 /// `userinfo` component always have `user` part (but it can be empty).
574 ///
575 /// Note that `("", None)` is considered as an empty userinfo, rather than
576 /// unset userinfo.
577 /// Also note that the user part cannot have colon characters.
578 ///
579 /// # Examples
580 ///
581 /// ```
582 /// # use iri_string::validate::Error;
583 /// use iri_string::build::Builder;
584 /// use iri_string::types::IriReferenceStr;
585 ///
586 /// let mut builder = Builder::new();
587 /// builder.userinfo("user:pass");
588 ///
589 /// let iri = builder.build::<IriReferenceStr>()?;
590 /// # #[cfg(feature = "alloc")] {
591 /// assert_eq!(iri.to_string(), "//user:pass@");
592 /// # }
593 /// # Ok::<_, Error>(())
594 /// ```
595 ///
596 /// You can specify `(user, password)` pair.
597 ///
598 /// ```
599 /// # use iri_string::validate::Error;
600 /// use iri_string::build::Builder;
601 /// use iri_string::types::IriReferenceStr;
602 ///
603 /// let mut builder = Builder::new();
604 ///
605 /// builder.userinfo(("user", Some("pass")));
606 /// # #[cfg(feature = "alloc")] {
607 /// assert_eq!(
608 /// builder.clone().build::<IriReferenceStr>()?.to_string(),
609 /// "//user:pass@"
610 /// );
611 /// # }
612 /// # Ok::<_, Error>(())
613 /// ```
614 ///
615 /// `("", None)` is considered as an empty userinfo.
616 ///
617 /// ```
618 /// # use iri_string::validate::Error;
619 /// use iri_string::build::Builder;
620 /// use iri_string::types::IriReferenceStr;
621 ///
622 /// let mut builder = Builder::new();
623 /// builder.userinfo(("", None));
624 ///
625 /// let iri = builder.build::<IriReferenceStr>()?;
626 /// # #[cfg(feature = "alloc")] {
627 /// assert_eq!(iri.to_string(), "//@");
628 /// # }
629 /// # Ok::<_, Error>(())
630 /// ```
631 #[inline]
632 pub fn userinfo<T: Into<UserinfoBuilder<'a>>>(&mut self, v: T) {
633 self.authority_builder().userinfo = v.into();
634 }
635
636 /// Unsets the port.
637 ///
638 /// # Examples
639 ///
640 /// ```
641 /// # use iri_string::validate::Error;
642 /// use iri_string::build::Builder;
643 /// use iri_string::types::IriReferenceStr;
644 ///
645 /// let mut builder = Builder::new();
646 /// builder.userinfo("user:pass");
647 /// // Note that this does not unset the entire authority.
648 /// // Now empty authority is set.
649 /// builder.unset_userinfo();
650 ///
651 /// let iri = builder.build::<IriReferenceStr>()?;
652 /// # #[cfg(feature = "alloc")] {
653 /// assert_eq!(iri.to_string(), "//");
654 /// # }
655 /// # Ok::<_, Error>(())
656 /// ```
657 #[inline]
658 pub fn unset_userinfo(&mut self) {
659 self.authority_builder().userinfo = UserinfoBuilder::default();
660 }
661
662 /// Sets the reg-name or IP address (i.e. host) without port.
663 ///
664 /// Note that no methods are provided to "unset" host.
665 /// Depending on your situation, set empty string as a reg-name, or unset
666 /// the authority entirely by [`unset_authority`][`Self::unset_authority`]
667 /// method.
668 ///
669 /// # Examples
670 ///
671 /// ```
672 /// # use iri_string::validate::Error;
673 /// use iri_string::build::Builder;
674 /// use iri_string::types::IriReferenceStr;
675 ///
676 /// let mut builder = Builder::new();
677 /// builder.host("example.com");
678 ///
679 /// let iri = builder.build::<IriReferenceStr>()?;
680 /// # #[cfg(feature = "alloc")] {
681 /// assert_eq!(iri.to_string(), "//example.com");
682 /// # }
683 /// # Ok::<_, Error>(())
684 /// ```
685 ///
686 /// The builder does **not** convert or encode the values automatically, so
687 /// the caller need to encode the host by yourself if they won't be valid
688 /// without additional encoding.
689 ///
690 /// ```
691 /// # use iri_string::validate::Error;
692 /// use iri_string::build::Builder;
693 /// use iri_string::percent_encode::PercentEncodedForUri;
694 /// use iri_string::types::UriReferenceStr;
695 ///
696 /// let mut builder = Builder::new();
697 /// builder.host("\u{03B1}.example.com");
698 /// assert!(
699 /// builder.build::<UriReferenceStr>().is_err(),
700 /// "a URI cannot have non-ASCII characters"
701 /// );
702 ///
703 /// let mut builder = Builder::new();
704 /// // Encode by yourself.
705 /// //
706 /// // Using percent-encoding here, but of course you can use another
707 /// // encoding such as IDNA.
708 /// let encoded_host =
709 /// PercentEncodedForUri::from_reg_name("\u{03B1}.example.com")
710 /// .to_string();
711 /// assert_eq!(encoded_host, "%CE%B1.example.com");
712 /// builder.host(&encoded_host);
713 /// let uri = builder.build::<UriReferenceStr>()?;
714 /// # #[cfg(feature = "alloc")] {
715 /// assert_eq!(uri.to_string(), "//%CE%B1.example.com");
716 /// # }
717 /// # Ok::<_, Error>(())
718 /// ```
719 #[inline]
720 pub fn host(&mut self, v: &'a str) {
721 self.authority_builder().host = HostRepr::String(v);
722 }
723
724 /// Sets the IP address as a host.
725 ///
726 /// Note that no methods are provided to "unset" host.
727 /// Depending on your situation, set empty string as a reg-name, or unset
728 /// the authority entirely by [`unset_authority`][`Self::unset_authority`]
729 /// method.
730 ///
731 /// # Examples
732 ///
733 /// ```
734 /// # use iri_string::validate::Error;
735 /// # #[cfg(feature = "std")] {
736 /// use iri_string::build::Builder;
737 /// use iri_string::types::IriReferenceStr;
738 ///
739 /// let mut builder = Builder::new();
740 /// builder.ip_address(std::net::Ipv4Addr::new(192, 0, 2, 0));
741 ///
742 /// let iri = builder.build::<IriReferenceStr>()?;
743 /// # #[cfg(feature = "alloc")] {
744 /// assert_eq!(iri.to_string(), "//192.0.2.0");
745 /// # }
746 /// # }
747 /// # Ok::<_, Error>(())
748 /// ```
749 #[cfg(feature = "std")]
750 #[inline]
751 pub fn ip_address<T: Into<std::net::IpAddr>>(&mut self, addr: T) {
752 self.authority_builder().host = HostRepr::IpAddr(addr.into());
753 }
754
755 /// Sets the port.
756 ///
757 /// # Examples
758 ///
759 /// ```
760 /// # use iri_string::validate::Error;
761 /// use iri_string::build::Builder;
762 /// use iri_string::types::IriReferenceStr;
763 ///
764 /// let mut builder = Builder::new();
765 /// builder.port(80_u16);
766 /// // Accepts other types that implements `Into<PortBuilder<'a>>`.
767 /// //builder.port(80_u8);
768 /// //builder.port("80");
769 ///
770 /// let iri = builder.build::<IriReferenceStr>()?;
771 /// # #[cfg(feature = "alloc")] {
772 /// assert_eq!(iri.to_string(), "//:80");
773 /// # }
774 /// # Ok::<_, Error>(())
775 /// ```
776 #[inline]
777 pub fn port<T: Into<PortBuilder<'a>>>(&mut self, v: T) {
778 self.authority_builder().port = v.into();
779 }
780
781 /// Unsets the port.
782 ///
783 /// # Examples
784 ///
785 /// ```
786 /// # use iri_string::validate::Error;
787 /// use iri_string::build::Builder;
788 /// use iri_string::types::IriReferenceStr;
789 ///
790 /// let mut builder = Builder::new();
791 /// builder.port(80_u16);
792 /// // Note that this does not unset the entire authority.
793 /// // Now empty authority is set.
794 /// builder.unset_port();
795 ///
796 /// let iri = builder.build::<IriReferenceStr>()?;
797 /// # #[cfg(feature = "alloc")] {
798 /// assert_eq!(iri.to_string(), "//");
799 /// # }
800 /// # Ok::<_, Error>(())
801 /// ```
802 #[inline]
803 pub fn unset_port(&mut self) {
804 self.authority_builder().port = PortBuilder::default();
805 }
806
807 /// Sets the query.
808 ///
809 /// The string after `?` should be specified.
810 ///
811 /// # Examples
812 ///
813 /// ```
814 /// # use iri_string::validate::Error;
815 /// use iri_string::build::Builder;
816 /// use iri_string::types::IriReferenceStr;
817 ///
818 /// let mut builder = Builder::new();
819 /// builder.query("q=example");
820 ///
821 /// let iri = builder.build::<IriReferenceStr>()?;
822 /// # #[cfg(feature = "alloc")] {
823 /// assert_eq!(iri.to_string(), "?q=example");
824 /// # }
825 /// # Ok::<_, Error>(())
826 /// ```
827 #[inline]
828 pub fn query(&mut self, v: &'a str) {
829 self.query = Some(v);
830 }
831
832 /// Unsets the query.
833 ///
834 /// # Examples
835 ///
836 /// ```
837 /// # use iri_string::validate::Error;
838 /// use iri_string::build::Builder;
839 /// use iri_string::types::IriReferenceStr;
840 ///
841 /// let mut builder = Builder::new();
842 /// builder.query("q=example");
843 /// builder.unset_query();
844 ///
845 /// let iri = builder.build::<IriReferenceStr>()?;
846 /// # #[cfg(feature = "alloc")] {
847 /// assert_eq!(iri.to_string(), "");
848 /// # }
849 /// # Ok::<_, Error>(())
850 /// ```
851 #[inline]
852 pub fn unset_query(&mut self) {
853 self.query = None;
854 }
855
856 /// Sets the fragment.
857 ///
858 /// The string after `#` should be specified.
859 ///
860 /// # Examples
861 ///
862 /// ```
863 /// # use iri_string::validate::Error;
864 /// use iri_string::build::Builder;
865 /// use iri_string::types::IriReferenceStr;
866 ///
867 /// let mut builder = Builder::new();
868 /// builder.fragment("anchor");
869 ///
870 /// let iri = builder.build::<IriReferenceStr>()?;
871 /// # #[cfg(feature = "alloc")] {
872 /// assert_eq!(iri.to_string(), "#anchor");
873 /// # }
874 /// # Ok::<_, Error>(())
875 /// ```
876 #[inline]
877 pub fn fragment(&mut self, v: &'a str) {
878 self.fragment = Some(v);
879 }
880
881 /// Unsets the fragment.
882 ///
883 /// # Examples
884 ///
885 /// ```
886 /// # use iri_string::validate::Error;
887 /// use iri_string::build::Builder;
888 /// use iri_string::types::IriReferenceStr;
889 ///
890 /// let mut builder = Builder::new();
891 /// builder.fragment("anchor");
892 /// builder.unset_fragment();
893 ///
894 /// let iri = builder.build::<IriReferenceStr>()?;
895 /// # #[cfg(feature = "alloc")] {
896 /// assert_eq!(iri.to_string(), "");
897 /// # }
898 /// # Ok::<_, Error>(())
899 /// ```
900 #[inline]
901 pub fn unset_fragment(&mut self) {
902 self.fragment = None;
903 }
904
905 /// Stop normalizing the result.
906 ///
907 /// # Examples
908 ///
909 /// ```
910 /// # use iri_string::validate::Error;
911 /// use iri_string::build::Builder;
912 /// use iri_string::types::IriReferenceStr;
913 ///
914 /// let mut builder = Builder::new();
915 /// builder.scheme("http");
916 /// // `%75%73%65%72` is "user".
917 /// builder.userinfo("%75%73%65%72");
918 /// builder.host("EXAMPLE.COM");
919 /// builder.port("");
920 /// builder.path("/foo/../%2e%2e/bar/%2e/baz/.");
921 ///
922 /// builder.unset_normalize();
923 ///
924 /// let iri = builder.build::<IriReferenceStr>()?;
925 /// # #[cfg(feature = "alloc")] {
926 /// assert_eq!(
927 /// iri.to_string(),
928 /// "http://%75%73%65%72@EXAMPLE.COM:/foo/../%2e%2e/bar/%2e/baz/."
929 /// );
930 /// # }
931 /// # Ok::<_, Error>(())
932 /// ```
933 #[inline]
934 pub fn unset_normalize(&mut self) {
935 self.normalize = false;
936 }
937
938 /// Normalizes the result using RFC 3986 syntax-based normalization and
939 /// WHATWG URL Standard algorithm.
940 ///
941 /// # Normalization
942 ///
943 /// If `scheme` or `authority` component is present or the path is absolute,
944 /// the build result will fully normalized using full syntax-based normalization:
945 ///
946 /// * case normalization ([RFC 3986 6.2.2.1]),
947 /// * percent-encoding normalization ([RFC 3986 6.2.2.2]), and
948 /// * path segment normalization ([RFC 3986 6.2.2.2]).
949 ///
950 /// However, if both `scheme` and `authority` is absent and the path is relative
951 /// (including empty), i.e. the IRI reference to be built starts with the
952 /// relative `path` component, path segment normalization will be omitted.
953 /// This is because the path segment normalization depends on presence or
954 /// absense of the `authority` components, and will remove extra `..`
955 /// segments which should not be ignored.
956 ///
957 /// Note that `path` must already be empty or start with a slash **before
958 /// the normalizaiton** if `authority` is present.
959 ///
960 /// # WHATWG URL Standard
961 ///
962 /// If you need to avoid WHATWG URL Standard serialization, use
963 /// [`Built::ensure_rfc3986_normalizable`] method to test if the result is
964 /// normalizable without WHATWG spec.
965 ///
966 /// # Examples
967 ///
968 /// From an empty builder:
969 ///
970 /// ```
971 /// # use iri_string::validate::Error;
972 /// use iri_string::build::Builder;
973 /// use iri_string::types::IriReferenceStr;
974 ///
975 /// let mut builder = Builder::new();
976 /// builder.scheme("http");
977 /// // `%75%73%65%72` is "user".
978 /// builder.userinfo("%75%73%65%72");
979 /// builder.host("EXAMPLE.COM");
980 /// builder.port("");
981 /// builder.path("/foo/../%2e%2e/bar/%2e/baz/.");
982 ///
983 /// builder.normalize();
984 ///
985 /// let iri = builder.build::<IriReferenceStr>()?;
986 /// # #[cfg(feature = "alloc")] {
987 /// assert_eq!(iri.to_string(), "http://user@example.com/bar/baz/");
988 /// # }
989 /// # Ok::<_, Error>(())
990 /// ```
991 ///
992 /// From an IRI:
993 ///
994 /// ```
995 /// # use iri_string::validate::Error;
996 /// use iri_string::build::Builder;
997 /// use iri_string::types::{IriReferenceStr, IriStr};
998 ///
999 /// // Using `IriStr` in this example, but you can use
1000 /// // other relative or absolute URI/IRI types too.
1001 /// let orig = IriStr::new("http://example.com/bar/")?;
1002 /// let mut builder = Builder::from(orig);
1003 /// // http -> https
1004 /// builder.scheme("https");
1005 /// builder.userinfo("username");
1006 ///
1007 /// let iri = builder.build::<IriReferenceStr>()?;
1008 /// # #[cfg(feature = "alloc")] {
1009 /// assert_eq!(iri.to_string(), "https://username@example.com/bar/");
1010 /// # }
1011 /// # Ok::<_, Error>(())
1012 /// ```
1013 #[inline]
1014 pub fn normalize(&mut self) {
1015 self.normalize = true;
1016 }
1017}
1018
1019impl<'a, S: Spec> From<&'a RiReferenceStr<S>> for Builder<'a> {
1020 fn from(iri: &'a RiReferenceStr<S>) -> Self {
1021 let (scheme, authority_str, path, query, fragment) =
1022 trusted_parser::decompose_iri_reference(iri).to_major();
1023 let authority = authority_str.map(|authority_str| {
1024 let authority_components =
1025 trusted_parser::authority::decompose_authority(authority_str);
1026
1027 AuthorityBuilder {
1028 host: HostRepr::String(authority_components.host()),
1029 port: authority_components
1030 .port()
1031 .map_or_else(Default::default, Into::into),
1032 userinfo: authority_components
1033 .userinfo()
1034 .map_or_else(Default::default, Into::into),
1035 }
1036 });
1037 Self {
1038 scheme,
1039 authority,
1040 path,
1041 query,
1042 fragment,
1043 normalize: false,
1044 }
1045 }
1046}
1047
1048impl<'a, S: Spec> From<&'a RiAbsoluteStr<S>> for Builder<'a> {
1049 #[inline]
1050 fn from(iri: &'a RiAbsoluteStr<S>) -> Self {
1051 Self::from(AsRef::<RiReferenceStr<S>>::as_ref(iri))
1052 }
1053}
1054
1055impl<'a, S: Spec> From<&'a RiRelativeStr<S>> for Builder<'a> {
1056 #[inline]
1057 fn from(iri: &'a RiRelativeStr<S>) -> Self {
1058 Self::from(AsRef::<RiReferenceStr<S>>::as_ref(iri))
1059 }
1060}
1061
1062impl<'a, S: Spec> From<&'a RiStr<S>> for Builder<'a> {
1063 #[inline]
1064 fn from(iri: &'a RiStr<S>) -> Self {
1065 Self::from(AsRef::<RiReferenceStr<S>>::as_ref(iri))
1066 }
1067}
1068
1069/// [`Display`]-able IRI build result.
1070///
1071/// The value of this type can generate an IRI using [`From`]/[`Into`] traits or
1072/// [`Display`] trait.
1073///
1074/// # Security consideration
1075///
1076/// This can be stringified or directly printed by `std::fmt::Display`, but note
1077/// that this `Display` **does not hide the password part**. Be careful **not to
1078/// print the value using `Display for Built<_>` in public context**.
1079///
1080/// [`From`]: `core::convert::From`
1081/// [`Into`]: `core::convert::Into`
1082/// [`Display`]: `core::fmt::Display`
1083#[derive(Debug)]
1084pub struct Built<'a, T: ?Sized> {
1085 /// Builder with the validated content.
1086 builder: Builder<'a>,
1087 /// Whether the path is absolute.
1088 path_is_absolute: bool,
1089 /// String type.
1090 _ty_str: PhantomData<fn() -> T>,
1091}
1092
1093impl<T: ?Sized> Clone for Built<'_, T> {
1094 #[inline]
1095 fn clone(&self) -> Self {
1096 Self {
1097 builder: self.builder.clone(),
1098 path_is_absolute: self.path_is_absolute,
1099 _ty_str: PhantomData,
1100 }
1101 }
1102}
1103
1104/// Implements conversions to a string.
1105macro_rules! impl_stringifiers {
1106 ($borrowed:ident, $owned:ident) => {
1107 impl<S: Spec> Built<'_, $borrowed<S>> {
1108 /// Returns Ok`(())` if the IRI is normalizable by the RFC 3986 algorithm.
1109 #[inline]
1110 pub fn ensure_rfc3986_normalizable(&self) -> Result<(), normalize::Error> {
1111 if self.builder.authority.is_none() {
1112 let path = normalize::PathToNormalize::from_single_path(self.builder.path);
1113 path.ensure_rfc3986_normalizable_with_authority_absent()?;
1114 }
1115 Ok(())
1116 }
1117 }
1118
1119 impl<S: Spec> fmt::Display for Built<'_, $borrowed<S>> {
1120 #[inline]
1121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1122 self.builder.fmt_write_to::<S>(f, self.path_is_absolute)
1123 }
1124 }
1125
1126 #[cfg(feature = "alloc")]
1127 impl<S: Spec> ToDedicatedString for Built<'_, $borrowed<S>> {
1128 type Target = $owned<S>;
1129
1130 #[inline]
1131 fn try_to_dedicated_string(&self) -> Result<Self::Target, TryReserveError> {
1132 let s = self.try_to_string()?;
1133 // SAFETY: `Built` will be returned to the user only when the
1134 // resulting string is valid as the target IRI type.
1135 Ok(unsafe {
1136 Self::Target::new_unchecked_justified(
1137 s,
1138 "the IRI to be built is already validated",
1139 )
1140 })
1141 }
1142 }
1143
1144 #[cfg(feature = "alloc")]
1145 impl<S: Spec> From<Built<'_, $borrowed<S>>> for $owned<S> {
1146 #[inline]
1147 fn from(builder: Built<'_, $borrowed<S>>) -> Self {
1148 (&builder).into()
1149 }
1150 }
1151
1152 #[cfg(feature = "alloc")]
1153 impl<S: Spec> From<&Built<'_, $borrowed<S>>> for $owned<S> {
1154 #[inline]
1155 fn from(builder: &Built<'_, $borrowed<S>>) -> Self {
1156 let s = builder.to_string();
1157 // SAFETY: `Built` will be returned to the user only when the
1158 // resulting string is valid as the target IRI type.
1159 unsafe {
1160 Self::new_unchecked_justified(s, "the IRI to be built is already validated")
1161 }
1162 }
1163 }
1164 };
1165}
1166
1167impl_stringifiers!(RiReferenceStr, RiReferenceString);
1168impl_stringifiers!(RiStr, RiString);
1169impl_stringifiers!(RiAbsoluteStr, RiAbsoluteString);
1170impl_stringifiers!(RiRelativeStr, RiRelativeString);
1171
1172/// A trait for borrowed IRI string types buildable by the [`Builder`].
1173pub trait Buildable<'a>: private::Sealed<'a> {}
1174
1175impl<'a, S: Spec> private::Sealed<'a> for RiReferenceStr<S> {
1176 fn validate_builder(builder: Builder<'a>) -> Result<Built<'a, Self>, Error> {
1177 let path_is_absolute = validate_builder_for_iri_reference::<S>(&builder)?;
1178
1179 Ok(Built {
1180 builder,
1181 path_is_absolute,
1182 _ty_str: PhantomData,
1183 })
1184 }
1185}
1186impl<S: Spec> Buildable<'_> for RiReferenceStr<S> {}
1187
1188impl<'a, S: Spec> private::Sealed<'a> for RiStr<S> {
1189 fn validate_builder(builder: Builder<'a>) -> Result<Built<'a, Self>, Error> {
1190 if builder.scheme.is_none() {
1191 return Err(Error::with_kind(ErrorKind::InvalidScheme));
1192 }
1193 let path_is_absolute = validate_builder_for_iri_reference::<S>(&builder)?;
1194
1195 Ok(Built {
1196 builder,
1197 path_is_absolute,
1198 _ty_str: PhantomData,
1199 })
1200 }
1201}
1202impl<S: Spec> Buildable<'_> for RiStr<S> {}
1203
1204impl<'a, S: Spec> private::Sealed<'a> for RiAbsoluteStr<S> {
1205 fn validate_builder(builder: Builder<'a>) -> Result<Built<'a, Self>, Error> {
1206 if builder.scheme.is_none() {
1207 return Err(Error::with_kind(ErrorKind::InvalidScheme));
1208 }
1209 if builder.fragment.is_some() {
1210 return Err(Error::with_kind(ErrorKind::UnexpectedFragment));
1211 }
1212 let path_is_absolute = validate_builder_for_iri_reference::<S>(&builder)?;
1213
1214 Ok(Built {
1215 builder,
1216 path_is_absolute,
1217 _ty_str: PhantomData,
1218 })
1219 }
1220}
1221impl<S: Spec> Buildable<'_> for RiAbsoluteStr<S> {}
1222
1223impl<'a, S: Spec> private::Sealed<'a> for RiRelativeStr<S> {
1224 fn validate_builder(builder: Builder<'a>) -> Result<Built<'a, Self>, Error> {
1225 if builder.scheme.is_some() {
1226 return Err(Error::with_kind(ErrorKind::UnexpectedAbsolute));
1227 }
1228 let path_is_absolute = validate_builder_for_iri_reference::<S>(&builder)?;
1229
1230 Ok(Built {
1231 builder,
1232 path_is_absolute,
1233 _ty_str: PhantomData,
1234 })
1235 }
1236}
1237impl<S: Spec> Buildable<'_> for RiRelativeStr<S> {}
1238
1239/// Checks whether the builder output is valid IRI reference.
1240///
1241/// Returns whether the path is absolute.
1242fn validate_builder_for_iri_reference<S: Spec>(builder: &Builder<'_>) -> Result<bool, Error> {
1243 if let Some(scheme) = builder.scheme {
1244 parser::validate_scheme(scheme)?;
1245 }
1246
1247 if let Some(authority) = &builder.authority {
1248 match &authority.userinfo.0 {
1249 UserinfoRepr::None => {}
1250 UserinfoRepr::Direct(userinfo) => {
1251 parser::validate_userinfo::<S>(userinfo)?;
1252 }
1253 UserinfoRepr::UserPass(user, password) => {
1254 // `user` is not allowed to have a colon, since the characters
1255 // after the colon is parsed as the password.
1256 if user.contains(':') {
1257 return Err(Error::with_kind(ErrorKind::InvalidUserInfo));
1258 }
1259
1260 // Note that the syntax of components inside `authority`
1261 // (`user` and `password`) is not specified by RFC 3986.
1262 parser::validate_userinfo::<S>(user)?;
1263 if let Some(password) = password {
1264 parser::validate_userinfo::<S>(password)?;
1265 }
1266 }
1267 }
1268
1269 match authority.host {
1270 HostRepr::String(s) => parser::validate_host::<S>(s)?,
1271 #[cfg(feature = "std")]
1272 HostRepr::IpAddr(_) => {}
1273 }
1274
1275 if let PortBuilderRepr::String(s) = authority.port.0 {
1276 if !s.bytes().all(|b| b.is_ascii_digit()) {
1277 return Err(Error::with_kind(ErrorKind::InvalidPort));
1278 }
1279 }
1280 }
1281
1282 let path_is_absolute: bool;
1283 let mut is_path_acceptable;
1284 if builder.normalize {
1285 if builder.scheme.is_some() || builder.authority.is_some() || builder.path.starts_with('/')
1286 {
1287 if builder.authority.is_some() {
1288 // Note that the path should already be in an absolute form before normalization.
1289 is_path_acceptable = builder.path.is_empty() || builder.path.starts_with('/');
1290 } else {
1291 is_path_acceptable = true;
1292 }
1293 let op = normalize::NormalizationOp {
1294 mode: NormalizationMode::Default,
1295 };
1296 let path_characteristic = PathCharacteristic::from_path_to_display::<S>(
1297 &normalize::PathToNormalize::from_single_path(builder.path),
1298 op,
1299 builder.authority.is_some(),
1300 );
1301 path_is_absolute = path_characteristic.is_absolute();
1302 is_path_acceptable = is_path_acceptable
1303 && match path_characteristic {
1304 PathCharacteristic::CommonAbsolute | PathCharacteristic::CommonRelative => true,
1305 PathCharacteristic::StartsWithDoubleSlash
1306 | PathCharacteristic::RelativeFirstSegmentHasColon => {
1307 builder.scheme.is_some() || builder.authority.is_some()
1308 }
1309 };
1310 } else {
1311 path_is_absolute = false;
1312 // If the path is relative (where neither scheme nor authority is
1313 // available), the first segment should not contain a colon.
1314 is_path_acceptable = prior_byte2(builder.path.as_bytes(), b'/', b':') != Some(b':');
1315 }
1316 } else {
1317 path_is_absolute = builder.path.starts_with('/');
1318 is_path_acceptable = if builder.authority.is_some() {
1319 // The path should be absolute or empty.
1320 path_is_absolute || builder.path.is_empty()
1321 } else if builder.scheme.is_some() || path_is_absolute {
1322 // The path should not start with '//'.
1323 !builder.path.starts_with("//")
1324 } else {
1325 // If the path is relative (where neither scheme nor authority is
1326 // available), the first segment should not contain a colon.
1327 prior_byte2(builder.path.as_bytes(), b'/', b':') != Some(b':')
1328 };
1329 }
1330 if !is_path_acceptable {
1331 return Err(Error::with_kind(ErrorKind::InvalidPath));
1332 }
1333
1334 if let Some(query) = builder.query {
1335 parser::validate_query::<S>(query)?;
1336 }
1337
1338 if let Some(fragment) = builder.fragment {
1339 parser::validate_fragment::<S>(fragment)?;
1340 }
1341
1342 Ok(path_is_absolute)
1343}
1344
1345/// Private module to put the trait to seal.
1346mod private {
1347 use super::{Builder, Built, Error};
1348
1349 /// A trait for types buildable by the [`Builder`].
1350 pub trait Sealed<'a> {
1351 /// Validates the content of the builder and returns the validated type if possible.
1352 fn validate_builder(builder: Builder<'a>) -> Result<Built<'a, Self>, Error>;
1353 }
1354}