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