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}