fluent_uri/build/
mod.rs

1//! Module for URI/IRI (reference) building.
2
3#![allow(missing_debug_implementations)]
4
5mod imp;
6pub(crate) mod state;
7
8use imp::*;
9use state::*;
10
11use crate::{
12    component::{Authority, Scheme},
13    imp::{Meta, RiMaybeRef},
14    pct_enc::EStr,
15};
16use alloc::string::String;
17use core::{fmt, marker::PhantomData};
18
19/// An error occurred when building a URI/IRI (reference).
20#[derive(Clone, Copy, Debug, Eq, PartialEq)]
21pub enum BuildError {
22    /// Authority is present, but the path is not empty and does not start with `'/'`.
23    NonemptyRootlessPath,
24    /// Authority is not present, but the path starts with `"//"`.
25    PathStartsWithDoubleSlash,
26    /// Neither scheme nor authority is present, but the first path segment contains `':'`.
27    FirstPathSegmentContainsColon,
28}
29
30impl fmt::Display for BuildError {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        let msg = match self {
33            Self::NonemptyRootlessPath => {
34                "when authority is present, path should either be empty or start with '/'"
35            }
36            Self::PathStartsWithDoubleSlash => {
37                "when authority is not present, path should not start with \"//\""
38            }
39            Self::FirstPathSegmentContainsColon => {
40                "when neither scheme nor authority is present, first path segment should not contain ':'"
41            }
42        };
43        f.write_str(msg)
44    }
45}
46
47#[cfg(feature = "impl-error")]
48impl crate::Error for BuildError {}
49
50/// A builder for URI/IRI (reference).
51///
52/// This struct is created by the `builder` associated
53/// functions on [`Uri`], [`UriRef`], [`Iri`], and [`IriRef`].
54///
55/// [`Uri`]: crate::Uri
56/// [`UriRef`]: crate::UriRef
57/// [`Iri`]: crate::Iri
58/// [`IriRef`]: crate::IriRef
59///
60/// # Examples
61///
62/// Basic usage:
63///
64/// ```
65/// use fluent_uri::{component::Scheme, pct_enc::EStr, Uri};
66///
67/// const SCHEME_FOO: &Scheme = Scheme::new_or_panic("foo");
68///
69/// let uri = Uri::builder()
70///     .scheme(SCHEME_FOO)
71///     .authority_with(|b| {
72///         b.userinfo(EStr::new_or_panic("user"))
73///             .host(EStr::new_or_panic("example.com"))
74///             .port(8042)
75///     })
76///     .path(EStr::new_or_panic("/over/there"))
77///     .query(EStr::new_or_panic("name=ferret"))
78///     .fragment(EStr::new_or_panic("nose"))
79///     .build()
80///     .unwrap();
81///
82/// assert_eq!(
83///     uri.as_str(),
84///     "foo://user@example.com:8042/over/there?name=ferret#nose"
85/// );
86/// ```
87///
88/// Note that [`EStr::new_or_panic`] *panics* on invalid input and
89/// should normally be used with constant strings.
90/// If you want to build a percent-encoded string from scratch,
91/// use [`EString`] instead.
92///
93/// [`EString`]: crate::pct_enc::EString
94///
95/// # Constraints
96///
97/// Typestates are used to avoid misconfigurations,
98/// which puts the following constraints:
99///
100/// - Components must be set from start to end, no repetition allowed.
101/// - Setting [`scheme`] is mandatory when building a URI/IRI.
102/// - Setting [`path`] is mandatory.
103/// - Methods [`userinfo`], [`host`], and [`port`] are only available
104///   within a call to [`authority_with`].
105/// - Setting [`host`] is mandatory within a call to [`authority_with`].
106///
107/// You may otherwise skip setting optional components
108/// (scheme, authority, userinfo, port, query, and fragment)
109/// with [`advance`] or set them optionally with [`optional`].
110///
111/// The builder typestates are currently private. Please open an issue
112/// if it is a problem not being able to name the type of a builder.
113///
114/// [`advance`]: Self::advance
115/// [`optional`]: Self::optional
116/// [`scheme`]: Self::scheme
117/// [`authority_with`]: Self::authority_with
118/// [`userinfo`]: Self::userinfo
119/// [`host`]: Self::host
120/// [`port`]: Self::port
121/// [`path`]: Self::path
122/// [`build`]: Self::build
123#[must_use]
124pub struct Builder<R, S> {
125    inner: BuilderInner,
126    _marker: PhantomData<(R, S)>,
127}
128
129impl<R, S> Builder<R, S> {
130    pub(crate) fn new() -> Self {
131        Self {
132            inner: BuilderInner {
133                buf: String::new(),
134                meta: Meta::default(),
135            },
136            _marker: PhantomData,
137        }
138    }
139}
140
141impl<R, S> Builder<R, S> {
142    fn cast<T>(self) -> Builder<R, T>
143    where
144        S: To<T>,
145    {
146        Builder {
147            inner: self.inner,
148            _marker: PhantomData,
149        }
150    }
151
152    /// Advances the builder state, skipping optional components in between.
153    ///
154    /// Variable rebinding may be necessary as this changes the type of the builder.
155    ///
156    /// ```
157    /// use fluent_uri::{component::Scheme, pct_enc::EStr, UriRef};
158    ///
159    /// fn build(relative: bool) -> UriRef<String> {
160    ///     let b = UriRef::builder();
161    ///     let b = if relative {
162    ///         b.advance()
163    ///     } else {
164    ///         b.scheme(Scheme::new_or_panic("http"))
165    ///             .authority_with(|b| b.host(EStr::new_or_panic("example.com")))
166    ///     };
167    ///     b.path(EStr::new_or_panic("/foo")).build().unwrap()
168    /// }
169    ///
170    /// assert_eq!(build(false).as_str(), "http://example.com/foo");
171    /// assert_eq!(build(true).as_str(), "/foo");
172    /// ```
173    pub fn advance<T>(self) -> Builder<R, T>
174    where
175        S: AdvanceTo<T>,
176    {
177        self.cast()
178    }
179
180    /// Optionally calls a builder method with a value.
181    ///
182    /// ```
183    /// use fluent_uri::{build::Builder, pct_enc::EStr, UriRef};
184    ///
185    /// let uri_ref = UriRef::builder()
186    ///     .path(EStr::new_or_panic("foo"))
187    ///     .optional(Builder::query, Some(EStr::new_or_panic("bar")))
188    ///     .optional(Builder::fragment, None)
189    ///     .build()
190    ///     .unwrap();
191    ///
192    /// assert_eq!(uri_ref.as_str(), "foo?bar");
193    /// ```
194    pub fn optional<F, V, T>(self, f: F, opt: Option<V>) -> Builder<R, T>
195    where
196        F: FnOnce(Self, V) -> Builder<R, T>,
197        S: AdvanceTo<T>,
198    {
199        match opt {
200            Some(value) => f(self, value),
201            None => self.advance(),
202        }
203    }
204}
205
206impl<R, S: To<SchemeEnd>> Builder<R, S> {
207    /// Sets the [scheme] component.
208    ///
209    /// Note that the scheme component is *case-insensitive* and its canonical form is
210    /// *lowercase*. For consistency, you should only produce lowercase scheme names.
211    ///
212    /// [scheme]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.1
213    pub fn scheme(mut self, scheme: &Scheme) -> Builder<R, SchemeEnd> {
214        self.inner.push_scheme(scheme.as_str());
215        self.cast()
216    }
217}
218
219impl<R: RiMaybeRef, S: To<AuthorityStart>> Builder<R, S> {
220    /// Builds the [authority] component with the given function.
221    ///
222    /// [authority]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2
223    pub fn authority_with<F, T>(mut self, f: F) -> Builder<R, AuthorityEnd>
224    where
225        F: FnOnce(Builder<R, AuthorityStart>) -> Builder<R, T>,
226        T: To<AuthorityEnd>,
227    {
228        self.inner.start_authority();
229        f(self.cast()).cast()
230    }
231
232    /// Sets the [authority] component.
233    ///
234    /// This method takes an [`Authority`] (for URI) or [`IAuthority`] (for IRI) as argument.
235    ///
236    /// This method is normally used with an authority which is empty ([`Authority::EMPTY`])
237    /// or is obtained from a URI/IRI (reference). If you need to build an authority from its
238    /// subcomponents (userinfo, host, and port), use [`authority_with`] instead.
239    ///
240    /// [authority]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2
241    /// [`IAuthority`]: crate::component::IAuthority
242    /// [`authority_with`]: Self::authority_with
243    ///
244    /// # Examples
245    ///
246    /// ```
247    /// use fluent_uri::{
248    ///     build::Builder,
249    ///     component::{Authority, Scheme},
250    ///     pct_enc::EStr,
251    ///     Uri,
252    /// };
253    ///
254    /// let uri = Uri::builder()
255    ///     .scheme(Scheme::new_or_panic("file"))
256    ///     .authority(Authority::EMPTY)
257    ///     .path(EStr::new_or_panic("/path/to/file"))
258    ///     .build()
259    ///     .unwrap();
260    ///
261    /// assert_eq!(uri, "file:///path/to/file");
262    ///
263    /// let auth = Uri::parse("foo://user@example.com:8042")?
264    ///     .authority()
265    ///     .unwrap();
266    /// let uri = Uri::builder()
267    ///     .scheme(Scheme::new_or_panic("http"))
268    ///     .authority(auth)
269    ///     .path(EStr::EMPTY)
270    ///     .build()
271    ///     .unwrap();
272    ///
273    /// assert_eq!(uri, "http://user@example.com:8042");
274    /// # Ok::<_, fluent_uri::ParseError>(())
275    /// ```
276    pub fn authority(
277        mut self,
278        authority: Authority<'_, R::UserinfoE, R::RegNameE>,
279    ) -> Builder<R, AuthorityEnd> {
280        self.inner.push_authority(authority.cast());
281        self.cast::<AuthorityEnd>()
282    }
283}
284
285impl<R: RiMaybeRef, S: To<UserinfoEnd>> Builder<R, S> {
286    /// Sets the [userinfo][userinfo-spec] subcomponent of authority.
287    ///
288    /// This method takes an <code>&amp;[EStr]&lt;[Userinfo]&gt;</code> (for URI)
289    /// or <code>&amp;[EStr]&lt;[IUserinfo]&gt;</code> (for IRI) as argument.
290    ///
291    /// [userinfo-spec]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.1
292    /// [Userinfo]: crate::pct_enc::encoder::Userinfo
293    /// [IUserinfo]: crate::pct_enc::encoder::IUserinfo
294    pub fn userinfo(mut self, userinfo: &EStr<R::UserinfoE>) -> Builder<R, UserinfoEnd> {
295        self.inner.push_userinfo(userinfo.as_str());
296        self.cast()
297    }
298}
299
300impl<R: RiMaybeRef, S: To<HostEnd>> Builder<R, S> {
301    /// Sets the [host] subcomponent of authority.
302    ///
303    /// This method takes either an [`Ipv4Addr`], [`Ipv6Addr`], [`IpAddr`],
304    /// <code>&amp;[EStr]&lt;[RegName]&gt;</code> (for URI)
305    /// or <code>&amp;[EStr]&lt;[IRegName]&gt;</code> (for IRI) as argument.
306    /// Crate feature `net` is required for this method to take an IP address as argument.
307    ///
308    /// If the contents of an input `EStr` slice matches the
309    /// `IPv4address` ABNF rule defined in [Section 3.2.2 of RFC 3986][host],
310    /// the resulting URI/IRI (reference) will output a [`Host::Ipv4`] variant instead.
311    ///
312    /// Note that ASCII characters within a host are *case-insensitive*.
313    /// For consistency, you should only produce [normalized] hosts.
314    ///
315    /// [host]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
316    /// [`Ipv4Addr`]: core::net::Ipv4Addr
317    /// [`Ipv6Addr`]: core::net::Ipv6Addr
318    /// [`IpAddr`]: core::net::IpAddr
319    /// [RegName]: crate::pct_enc::encoder::RegName
320    /// [IRegName]: crate::pct_enc::encoder::IRegName
321    /// [`Host::Ipv4`]: crate::component::Host::Ipv4
322    /// [normalized]: crate::Uri::normalize
323    ///
324    /// # Examples
325    ///
326    /// ```
327    /// use fluent_uri::{component::Host, pct_enc::EStr, UriRef};
328    ///
329    /// let uri_ref = UriRef::builder()
330    ///     .authority_with(|b| b.host(EStr::new_or_panic("127.0.0.1")))
331    ///     .path(EStr::EMPTY)
332    ///     .build()
333    ///     .unwrap();
334    ///
335    /// assert!(matches!(uri_ref.authority().unwrap().host_parsed(), Host::Ipv4 { .. }));
336    /// ```
337    pub fn host<'a>(
338        mut self,
339        host: impl AsHost<'a> + WithEncoder<R::RegNameE>,
340    ) -> Builder<R, HostEnd> {
341        host.push_to(&mut self.inner);
342        self.cast()
343    }
344}
345
346impl<R, S: To<PortEnd>> Builder<R, S> {
347    /// Sets the [port][port-spec] subcomponent of authority.
348    ///
349    /// This method takes either a `u16` or <code>&amp;[EStr]&lt;[Port]&gt;</code> as argument.
350    ///
351    /// For consistency, you should not produce an empty or [default] port.
352    ///
353    /// [port-spec]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3
354    /// [Port]: crate::pct_enc::encoder::Port
355    /// [default]: Scheme::default_port
356    pub fn port(mut self, port: impl AsPort) -> Builder<R, PortEnd> {
357        port.push_to(&mut self.inner.buf);
358        self.cast()
359    }
360
361    /// Sets the [port] subcomponent of authority, omitting it when it equals the default value.
362    ///
363    /// [port]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3
364    #[cfg(fluent_uri_unstable)]
365    pub fn port_with_default(self, port: u16, default: u16) -> Builder<R, PortEnd> {
366        if port != default {
367            self.cast()
368        } else {
369            self.port(port)
370        }
371    }
372}
373
374impl<R: RiMaybeRef, S: To<PathEnd>> Builder<R, S> {
375    /// Sets the [path][path-spec] component.
376    ///
377    /// This method takes an <code>&amp;[EStr]&lt;[Path]&gt;</code> (for URI)
378    /// or <code>&amp;[EStr]&lt;[IPath]&gt;</code> (for IRI) as argument.
379    ///
380    /// [path-spec]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.3
381    /// [Path]: crate::pct_enc::encoder::Path
382    /// [IPath]: crate::pct_enc::encoder::IPath
383    pub fn path(mut self, path: &EStr<R::PathE>) -> Builder<R, PathEnd> {
384        self.inner.push_path(path.as_str());
385        self.cast()
386    }
387}
388
389impl<R: RiMaybeRef, S: To<QueryEnd>> Builder<R, S> {
390    /// Sets the [query][query-spec] component.
391    ///
392    /// This method takes an <code>&amp;[EStr]&lt;[Query]&gt;</code> (for URI)
393    /// or <code>&amp;[EStr]&lt;[IQuery]&gt;</code> (for IRI) as argument.
394    ///
395    /// [query-spec]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.4
396    /// [Query]: crate::pct_enc::encoder::Query
397    /// [IQuery]: crate::pct_enc::encoder::IQuery
398    pub fn query(mut self, query: &EStr<R::QueryE>) -> Builder<R, QueryEnd> {
399        self.inner.push_query(query.as_str());
400        self.cast()
401    }
402}
403
404impl<R: RiMaybeRef, S: To<FragmentEnd>> Builder<R, S> {
405    /// Sets the [fragment][fragment-spec] component.
406    ///
407    /// This method takes an <code>&amp;[EStr]&lt;[Fragment]&gt;</code> (for URI)
408    /// or <code>&amp;[EStr]&lt;[IFragment]&gt;</code> (for IRI) as argument.
409    ///
410    /// [fragment-spec]: https://datatracker.ietf.org/doc/html/rfc3986#section-3.5
411    /// [Fragment]: crate::pct_enc::encoder::Fragment
412    /// [IFragment]: crate::pct_enc::encoder::IFragment
413    pub fn fragment(mut self, fragment: &EStr<R::FragmentE>) -> Builder<R, FragmentEnd> {
414        self.inner.push_fragment(fragment.as_str());
415        self.cast()
416    }
417}
418
419impl<R: RiMaybeRef<Val = String>, S: To<End>> Builder<R, S> {
420    /// Builds the URI/IRI (reference).
421    ///
422    /// # Errors
423    ///
424    /// Returns `Err` if any of the following conditions is not met.
425    ///
426    /// - When authority is present, the path must either be empty or start with `'/'`.
427    /// - When authority is not present, the path cannot start with `"//"`.
428    /// - When neither scheme nor authority is present, the first path segment cannot contain `':'`.
429    ///
430    /// [rel-ref]: https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
431    pub fn build(self) -> Result<R, BuildError> {
432        self.inner
433            .validate()
434            .map(|()| R::new(self.inner.buf, self.inner.meta))
435    }
436}