fluent_uri/builder/
mod.rs

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