Skip to main content

fuchsia_url/
generic.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::fmt::Display as _;
6
7/// Sealed trait for URL schemes.
8/// Allows having separate types for URLs with schemes that are more constrained than
9/// ['Scheme`](crate::Scheme).
10#[allow(private_bounds)]
11pub trait SchemeTrait: Sized + std::fmt::Display + std::clone::Clone + crate::Sealer {
12    fn try_from_part(scheme: crate::Scheme) -> Result<Self, crate::ParseError>;
13}
14
15/// Sealed trait for URL hosts.
16/// Allows having separate types for URLs with and without hosts.
17#[allow(private_bounds)]
18pub trait HostTrait: Sized + std::fmt::Display + std::clone::Clone + crate::Sealer {
19    fn try_from_part(host: Option<crate::Host>) -> Result<Self, crate::ParseError>;
20}
21
22/// Sealed trait for URL paths.
23/// Allows having separate types for URLs with paths that are more constrained than
24/// [`Path`](crate::Path).
25#[allow(private_bounds)]
26pub trait PathTrait: Sized + std::clone::Clone + crate::Sealer {
27    fn try_from_part(path: Option<crate::Path>) -> Result<Self, crate::ParseError>;
28    fn is_present(&self) -> bool;
29    fn url_display(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
30}
31
32/// Sealed trait for URL hashes.
33/// Allows having separate types for URLs with and without hashes (called pinned and unpinned).
34#[allow(private_bounds)]
35pub trait HashTrait: Sized + std::clone::Clone + crate::Sealer {
36    fn try_from_part(hash: Option<crate::Hash>) -> Result<Self, crate::ParseError>;
37    fn is_present(&self) -> bool;
38    fn url_display(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
39}
40
41/// Building block for creating component URL types. To use create implementations of the type
42/// variables that behave as desired and use them in a type alias.
43#[derive(Debug, Clone)]
44pub enum ComponentUrl<SCHEME, HOST, PATH, HASH> {
45    Absolute(AbsoluteComponentUrl<SCHEME, HOST, PATH, HASH>),
46    Relative(crate::RelativeComponentUrl),
47}
48
49impl<SCHEME, HOST, PATH, HASH> ComponentUrl<SCHEME, HOST, PATH, HASH>
50where
51    SCHEME: SchemeTrait,
52    HOST: HostTrait,
53    PATH: PathTrait,
54    HASH: HashTrait,
55{
56    /// Create a URL from a `&str`.
57    pub fn parse(url: &str) -> Result<Self, crate::ParseError> {
58        let parts = crate::UrlParts::parse(url)?;
59        Ok(if parts.scheme.is_some() {
60            Self::Absolute(AbsoluteComponentUrl::try_from_parts(parts)?)
61        } else {
62            Self::Relative(crate::RelativeComponentUrl::from_parts(parts)?)
63        })
64    }
65
66    /// Obtain a reference to the URL's resource.
67    pub fn resource(&self) -> &crate::Resource {
68        match self {
69            Self::Absolute(absolute) => &absolute.resource,
70            Self::Relative(relative) => &relative.resource(),
71        }
72    }
73
74    /// Create a package URL from this URL. Equivalent to this URL without the resource.
75    pub fn to_package_url(&self) -> PackageUrl<SCHEME, HOST, PATH, HASH> {
76        match self {
77            Self::Absolute(absolute) => PackageUrl::Absolute(absolute.to_package_url()),
78            Self::Relative(relative) => PackageUrl::Relative(relative.package_url().clone()),
79        }
80    }
81}
82
83impl<SCHEME, HOST, PATH, HASH> std::fmt::Display for ComponentUrl<SCHEME, HOST, PATH, HASH>
84where
85    SCHEME: SchemeTrait,
86    HOST: HostTrait,
87    PATH: PathTrait,
88    HASH: HashTrait,
89{
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        match self {
92            Self::Absolute(absolute) => absolute.fmt(f),
93            Self::Relative(relative) => relative.fmt(f),
94        }
95    }
96}
97
98/// Building block for creating component URL types. To use create implementations of the type
99/// variables that behave as desired and use them in a type alias.
100#[derive(Debug, Clone)]
101pub struct AbsoluteComponentUrl<SCHEME, HOST, PATH, HASH> {
102    scheme: SCHEME,
103    host: HOST,
104    path: PATH,
105    hash: HASH,
106    resource: crate::Resource,
107}
108
109impl<SCHEME, HOST, PATH, HASH> AbsoluteComponentUrl<SCHEME, HOST, PATH, HASH>
110where
111    SCHEME: SchemeTrait,
112    HOST: HostTrait,
113    PATH: PathTrait,
114    HASH: HashTrait,
115{
116    /// Create a URL from a `&str`.
117    pub fn parse(url: &str) -> Result<Self, crate::ParseError> {
118        let parts = crate::UrlParts::parse(url)?;
119        Self::try_from_parts(parts)
120    }
121
122    /// Creates a URL from its constituent parts.
123    pub fn from_parts(
124        scheme: SCHEME,
125        host: HOST,
126        path: PATH,
127        hash: HASH,
128        resource: crate::Resource,
129    ) -> Self {
130        Self { scheme, host, path, hash, resource }
131    }
132
133    fn try_from_parts(parts: crate::UrlParts) -> Result<Self, crate::ParseError> {
134        let crate::UrlParts { scheme, host, path, hash, resource } = parts;
135        let scheme = SCHEME::try_from_part(scheme.ok_or(crate::ParseError::MissingScheme)?)?;
136        let host = HOST::try_from_part(host)?;
137        let path = PATH::try_from_part(path)?;
138        let hash = HASH::try_from_part(hash)?;
139        let Some(resource) = resource else {
140            return Err(crate::ParseError::MissingResource);
141        };
142        Ok(Self { scheme, host, path, hash, resource })
143    }
144
145    /// Obtain a reference to the URL's path.
146    pub fn path(&self) -> &PATH {
147        &self.path
148    }
149
150    /// Obtain a reference to the URL's resource.
151    pub fn resource(&self) -> &crate::Resource {
152        &self.resource
153    }
154
155    /// Create an [`AbsolutePackageUrl`] from this URL, which is equal to this URL without its
156    /// [`Resource`](crate::Resource).
157    pub fn to_package_url(&self) -> AbsolutePackageUrl<SCHEME, HOST, PATH, HASH> {
158        AbsolutePackageUrl::<SCHEME, HOST, PATH, HASH> {
159            scheme: self.scheme.clone(),
160            host: self.host.clone(),
161            path: self.path.clone(),
162            hash: self.hash.clone(),
163        }
164    }
165}
166
167impl<SCHEME, HOST, PATH, HASH> std::fmt::Display for AbsoluteComponentUrl<SCHEME, HOST, PATH, HASH>
168where
169    SCHEME: SchemeTrait,
170    HOST: HostTrait,
171    PATH: PathTrait,
172    HASH: HashTrait,
173{
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        let () = write!(f, "{}://{}", self.scheme, self.host)?;
176        if self.path.is_present() {
177            let () = f.write_str("/")?;
178            let () = self.path.url_display(f)?;
179        }
180        if self.hash.is_present() {
181            let () = f.write_str("?hash=")?;
182            let () = self.hash.url_display(f)?;
183        }
184        let () = write!(f, "#{}", self.resource)?;
185        Ok(())
186    }
187}
188
189/// Building block for creating package URL types. To use create implementations of the type
190/// variables that behave as desired and use them in a type alias.
191pub enum PackageUrl<SCHEME, HOST, PATH, HASH> {
192    Absolute(AbsolutePackageUrl<SCHEME, HOST, PATH, HASH>),
193    Relative(crate::RelativePackageUrl),
194}
195
196impl<SCHEME, HOST, PATH, HASH> PackageUrl<SCHEME, HOST, PATH, HASH>
197where
198    SCHEME: SchemeTrait,
199    HOST: HostTrait,
200    PATH: PathTrait,
201    HASH: HashTrait,
202{
203    /// Create a URL from a `&str`.
204    pub fn parse(url: &str) -> Result<Self, crate::ParseError> {
205        let parts = crate::UrlParts::parse(url)?;
206        Ok(if parts.scheme.is_some() {
207            Self::Absolute(AbsolutePackageUrl::try_from_parts(parts)?)
208        } else {
209            Self::Relative(crate::RelativePackageUrl::from_parts(parts)?)
210        })
211    }
212}
213
214impl<SCHEME, HOST, PATH, HASH> std::fmt::Display for PackageUrl<SCHEME, HOST, PATH, HASH>
215where
216    SCHEME: SchemeTrait,
217    HOST: HostTrait,
218    PATH: PathTrait,
219    HASH: HashTrait,
220{
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        match self {
223            Self::Absolute(absolute) => absolute.fmt(f),
224            Self::Relative(relative) => relative.fmt(f),
225        }
226    }
227}
228
229/// Building block for creating package URL types. To use create implementations of the type
230/// variables that behave as desired and use them in a type alias.
231#[derive(Debug, Clone)]
232pub struct AbsolutePackageUrl<SCHEME, HOST, PATH, HASH> {
233    scheme: SCHEME,
234    host: HOST,
235    path: PATH,
236    hash: HASH,
237}
238
239impl<SCHEME, HOST, PATH, HASH> AbsolutePackageUrl<SCHEME, HOST, PATH, HASH>
240where
241    SCHEME: SchemeTrait,
242    HOST: HostTrait,
243    PATH: PathTrait,
244    HASH: HashTrait,
245{
246    /// Create a URL from a `&str`.
247    pub fn parse(url: &str) -> Result<Self, crate::ParseError> {
248        let parts = crate::UrlParts::parse(url)?;
249        Self::try_from_parts(parts)
250    }
251
252    fn try_from_parts(parts: crate::UrlParts) -> Result<Self, crate::ParseError> {
253        let crate::UrlParts { scheme, host, path, hash, resource } = parts;
254        let scheme = SCHEME::try_from_part(scheme.ok_or(crate::ParseError::MissingScheme)?)?;
255        let host = HOST::try_from_part(host)?;
256        let path = PATH::try_from_part(path)?;
257        let hash = HASH::try_from_part(hash)?;
258        if resource.is_some() {
259            return Err(crate::ParseError::CannotContainResource);
260        }
261        Ok(Self { scheme, host, path, hash })
262    }
263
264    /// Obtain a reference to the URL's path.
265    pub fn path(&self) -> &PATH {
266        &self.path
267    }
268}
269
270impl<SCHEME, HOST, PATH, HASH> std::fmt::Display for AbsolutePackageUrl<SCHEME, HOST, PATH, HASH>
271where
272    SCHEME: SchemeTrait,
273    HOST: HostTrait,
274    PATH: PathTrait,
275    HASH: HashTrait,
276{
277    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
278        let () = write!(f, "{}://{}", self.scheme, self.host)?;
279        if self.path.is_present() {
280            let () = f.write_str("/")?;
281            let () = self.path.url_display(f)?;
282        }
283        if self.hash.is_present() {
284            let () = f.write_str("?hash=")?;
285            let () = self.hash.url_display(f)?;
286        }
287        Ok(())
288    }
289}
290
291/// Type for URLs that do not have any additional constraints on their scheme.
292impl crate::Sealer for crate::Scheme {}
293impl SchemeTrait for crate::Scheme {
294    fn try_from_part(scheme: crate::Scheme) -> Result<Self, crate::ParseError> {
295        Ok(scheme)
296    }
297}
298
299/// Type for URLs that do not have a host.
300#[derive(Debug, Clone)]
301pub struct NoneHost;
302impl crate::Sealer for NoneHost {}
303impl HostTrait for NoneHost {
304    fn try_from_part(host: Option<crate::Host>) -> Result<Self, crate::ParseError> {
305        match host {
306            None => Ok(Self),
307            _ => Err(crate::ParseError::HostMustBeEmpty),
308        }
309    }
310}
311impl std::fmt::Display for NoneHost {
312    fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313        Ok(())
314    }
315}
316
317/// Type for URLs that might have a host.
318#[derive(Debug, Clone)]
319pub struct OptionHost(Option<crate::Host>);
320impl crate::Sealer for OptionHost {}
321impl HostTrait for OptionHost {
322    fn try_from_part(host: Option<crate::Host>) -> Result<Self, crate::ParseError> {
323        Ok(Self(host))
324    }
325}
326impl std::fmt::Display for OptionHost {
327    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328        if let Some(host) = &self.0 { host.fmt(f) } else { Ok(()) }
329    }
330}
331
332impl crate::Sealer for Option<crate::Path> {}
333impl PathTrait for Option<crate::Path> {
334    fn try_from_part(path: Option<crate::Path>) -> Result<Self, crate::ParseError> {
335        Ok(path)
336    }
337    fn is_present(&self) -> bool {
338        self.is_some()
339    }
340    fn url_display(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
341        if let Some(path) = &self { path.fmt(f) } else { Ok(()) }
342    }
343}
344
345/// Type for URLs that do not have a hash (are unpinned).
346#[derive(Debug, Clone)]
347pub struct NoneHash;
348impl crate::Sealer for NoneHash {}
349impl HashTrait for NoneHash {
350    fn try_from_part(hash: Option<crate::Hash>) -> Result<Self, crate::ParseError> {
351        match hash {
352            None => Ok(Self),
353            _ => Err(crate::ParseError::CannotContainHash),
354        }
355    }
356    fn is_present(&self) -> bool {
357        false
358    }
359    fn url_display(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
360        Ok(())
361    }
362}
363
364impl crate::Sealer for Option<crate::Hash> {}
365impl HashTrait for Option<crate::Hash> {
366    fn try_from_part(hash: Option<crate::Hash>) -> Result<Self, crate::ParseError> {
367        Ok(hash)
368    }
369    fn is_present(&self) -> bool {
370        self.is_some()
371    }
372    fn url_display(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373        if let Some(hash) = &self { hash.fmt(f) } else { Ok(()) }
374    }
375}