1use crate::errors::ParseError;
6use crate::parse::{PackageName, PackageVariant};
7use crate::{
8 FuchsiaPkgPinnedAbsolutePackageUrl, FuchsiaPkgUnpinnedAbsolutePackageUrl, RepositoryUrl,
9 UrlParts,
10};
11use fuchsia_hash::Hash;
12
13#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
21pub enum FuchsiaPkgAbsolutePackageUrl {
22 Unpinned(FuchsiaPkgUnpinnedAbsolutePackageUrl),
23 Pinned(FuchsiaPkgPinnedAbsolutePackageUrl),
24}
25
26impl FuchsiaPkgAbsolutePackageUrl {
27 pub(crate) fn from_parts(parts: UrlParts) -> Result<Self, ParseError> {
28 let UrlParts { scheme, host, path, hash, resource } = parts;
29 let repo = RepositoryUrl::new(
30 scheme.ok_or(ParseError::MissingScheme)?,
31 host.ok_or(ParseError::MissingHost)?,
32 )?;
33 if resource.is_some() {
34 return Err(ParseError::CannotContainResource);
35 }
36 let Some(path) = path else {
37 return Err(ParseError::MissingName);
38 };
39 Self::new_with_path(repo, path.as_ref(), hash)
40 }
41
42 pub fn parse(url: &str) -> Result<Self, ParseError> {
44 Self::from_parts(UrlParts::parse(url)?)
45 }
46
47 pub fn from_url(url: &url::Url) -> Result<Self, ParseError> {
48 Self::from_parts(UrlParts::from_url(url)?)
49 }
50
51 pub fn new_with_path(
54 repo: RepositoryUrl,
55 path: &str,
56 hash: Option<Hash>,
57 ) -> Result<Self, ParseError> {
58 Ok(match hash {
59 None => {
60 Self::Unpinned(FuchsiaPkgUnpinnedAbsolutePackageUrl::new_with_path(repo, path)?)
61 }
62 Some(hash) => {
63 Self::Pinned(FuchsiaPkgPinnedAbsolutePackageUrl::new_with_path(repo, path, hash)?)
64 }
65 })
66 }
67
68 pub fn new(
70 repo: RepositoryUrl,
71 name: PackageName,
72 variant: Option<PackageVariant>,
73 hash: Option<Hash>,
74 ) -> Self {
75 match hash {
76 None => Self::Unpinned(FuchsiaPkgUnpinnedAbsolutePackageUrl::new(repo, name, variant)),
77 Some(hash) => {
78 Self::Pinned(FuchsiaPkgPinnedAbsolutePackageUrl::new(repo, name, variant, hash))
79 }
80 }
81 }
82
83 pub fn hash(&self) -> Option<Hash> {
85 match self {
86 Self::Unpinned(_) => None,
87 Self::Pinned(pinned) => Some(pinned.hash()),
88 }
89 }
90
91 pub fn name(&self) -> &PackageName {
92 match self {
93 Self::Unpinned(unpinned) => &unpinned.name(),
94 Self::Pinned(pinned) => pinned.name(),
95 }
96 }
97
98 pub fn as_unpinned(&self) -> &FuchsiaPkgUnpinnedAbsolutePackageUrl {
100 match self {
101 Self::Unpinned(unpinned) => &unpinned,
102 Self::Pinned(pinned) => pinned.as_unpinned(),
103 }
104 }
105
106 pub fn pinned(self) -> Option<FuchsiaPkgPinnedAbsolutePackageUrl> {
108 match self {
109 Self::Unpinned(_) => None,
110 Self::Pinned(pinned) => Some(pinned),
111 }
112 }
113}
114
115impl std::ops::Deref for FuchsiaPkgAbsolutePackageUrl {
118 type Target = FuchsiaPkgUnpinnedAbsolutePackageUrl;
119
120 fn deref(&self) -> &Self::Target {
121 match self {
122 Self::Unpinned(unpinned) => &unpinned,
123 Self::Pinned(pinned) => &pinned,
124 }
125 }
126}
127
128impl std::ops::DerefMut for FuchsiaPkgAbsolutePackageUrl {
131 fn deref_mut(&mut self) -> &mut Self::Target {
132 match self {
133 Self::Unpinned(unpinned) => unpinned,
134 Self::Pinned(pinned) => pinned,
135 }
136 }
137}
138
139impl std::str::FromStr for FuchsiaPkgAbsolutePackageUrl {
140 type Err = ParseError;
141
142 fn from_str(url: &str) -> Result<Self, Self::Err> {
143 Self::parse(url)
144 }
145}
146
147impl std::convert::TryFrom<&str> for FuchsiaPkgAbsolutePackageUrl {
148 type Error = ParseError;
149
150 fn try_from(value: &str) -> Result<Self, Self::Error> {
151 Self::parse(value)
152 }
153}
154
155impl std::convert::From<FuchsiaPkgPinnedAbsolutePackageUrl> for FuchsiaPkgAbsolutePackageUrl {
156 fn from(pinned: FuchsiaPkgPinnedAbsolutePackageUrl) -> Self {
157 Self::Pinned(pinned)
158 }
159}
160
161impl std::convert::From<FuchsiaPkgUnpinnedAbsolutePackageUrl> for FuchsiaPkgAbsolutePackageUrl {
162 fn from(unpinned: FuchsiaPkgUnpinnedAbsolutePackageUrl) -> Self {
163 Self::Unpinned(unpinned)
164 }
165}
166
167impl std::fmt::Display for FuchsiaPkgAbsolutePackageUrl {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 match self {
170 Self::Unpinned(unpinned) => write!(f, "{}", unpinned),
171 Self::Pinned(pinned) => write!(f, "{}", pinned),
172 }
173 }
174}
175
176impl serde::Serialize for FuchsiaPkgAbsolutePackageUrl {
177 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
178 self.to_string().serialize(ser)
179 }
180}
181
182impl<'de> serde::Deserialize<'de> for FuchsiaPkgAbsolutePackageUrl {
183 fn deserialize<D>(de: D) -> Result<Self, D::Error>
184 where
185 D: serde::Deserializer<'de>,
186 {
187 let url = String::deserialize(de)?;
188 Ok(Self::parse(&url).map_err(|err| serde::de::Error::custom(err))?)
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use crate::errors::PackagePathSegmentError;
196 use assert_matches::assert_matches;
197 use std::convert::TryFrom as _;
198
199 #[test]
200 fn parse_err() {
201 for (url, err) in [
202 ("example.org/name", ParseError::MissingScheme),
203 ("//example.org/name", ParseError::MissingScheme),
204 ("///name", ParseError::MissingScheme),
205 ("/name", ParseError::MissingScheme),
206 ("name", ParseError::MissingScheme),
207 ("fuchsia-boot://example.org/name", ParseError::InvalidScheme),
208 ("fuchsia-pkg://", ParseError::MissingHost),
209 ("fuchsia-pkg://exaMple.org", ParseError::InvalidHost),
210 ("fuchsia-pkg://example.org/", ParseError::MissingName),
211 (
212 "fuchsia-pkg://example.org//",
213 ParseError::InvalidPathSegment(PackagePathSegmentError::Empty),
214 ),
215 ("fuchsia-pkg://example.org/name/variant/extra", ParseError::ExtraPathSegments),
216 ("fuchsia-pkg://example.org/name#resource", ParseError::CannotContainResource),
217 ] {
218 assert_matches!(
219 FuchsiaPkgAbsolutePackageUrl::parse(url),
220 Err(e) if e == err,
221 "the url {:?}", url
222 );
223 assert_matches!(
224 url.parse::<FuchsiaPkgAbsolutePackageUrl>(),
225 Err(e) if e == err,
226 "the url {:?}", url
227 );
228 assert_matches!(
229 FuchsiaPkgAbsolutePackageUrl::try_from(url),
230 Err(e) if e == err,
231 "the url {:?}", url
232 );
233 assert_matches!(
234 serde_json::from_str::<FuchsiaPkgAbsolutePackageUrl>(url),
235 Err(_),
236 "the url {:?}",
237 url
238 );
239 }
240 }
241
242 #[test]
243 fn parse_ok() {
244 for (url, host, name, variant, hash) in [
245 ("fuchsia-pkg://example.org/name", "example.org", "name", None, None),
246 (
247 "fuchsia-pkg://example.org/name/variant",
248 "example.org",
249 "name",
250 Some("variant"),
251 None,
252 ),
253 (
254 "fuchsia-pkg://example.org/name?hash=0000000000000000000000000000000000000000000000000000000000000000",
255 "example.org",
256 "name",
257 None,
258 Some("0000000000000000000000000000000000000000000000000000000000000000"),
259 ),
260 (
261 "fuchsia-pkg://example.org/name/variant?hash=0000000000000000000000000000000000000000000000000000000000000000",
262 "example.org",
263 "name",
264 Some("variant"),
265 Some("0000000000000000000000000000000000000000000000000000000000000000"),
266 ),
267 ] {
268 let json_url = format!("\"{url}\"");
269
270 let name = name.parse::<crate::PackageName>().unwrap();
272 let variant = variant.map(|v| v.parse::<crate::PackageVariant>().unwrap());
273 let hash = hash.map(|h| h.parse::<Hash>().unwrap());
274 let validate = |parsed: &FuchsiaPkgAbsolutePackageUrl| {
275 assert_eq!(parsed.host(), host);
276 assert_eq!(parsed.name(), &name);
277 assert_eq!(parsed.variant(), variant.as_ref());
278 assert_eq!(parsed.hash(), hash);
279 };
280 validate(&FuchsiaPkgAbsolutePackageUrl::parse(url).unwrap());
281 validate(&url.parse::<FuchsiaPkgAbsolutePackageUrl>().unwrap());
282 validate(&FuchsiaPkgAbsolutePackageUrl::try_from(url).unwrap());
283 validate(&serde_json::from_str::<FuchsiaPkgAbsolutePackageUrl>(&json_url).unwrap());
284
285 assert_eq!(
287 FuchsiaPkgAbsolutePackageUrl::parse(url).unwrap().to_string(),
288 url,
289 "the url {:?}",
290 url
291 );
292 assert_eq!(
293 serde_json::to_string(&FuchsiaPkgAbsolutePackageUrl::parse(url).unwrap()).unwrap(),
294 json_url,
295 "the url {:?}",
296 url
297 );
298 }
299 }
300}