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>&[EStr]<[Userinfo]></code> (for URI)
289 /// or <code>&[EStr]<[IUserinfo]></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>&[EStr]<[RegName]></code> (for URI)
305 /// or <code>&[EStr]<[IRegName]></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>&[EStr]<[Port]></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>&[EStr]<[Path]></code> (for URI)
378 /// or <code>&[EStr]<[IPath]></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>&[EStr]<[Query]></code> (for URI)
393 /// or <code>&[EStr]<[IQuery]></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>&[EStr]<[Fragment]></code> (for URI)
408 /// or <code>&[EStr]<[IFragment]></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}